skillscript-runtime 0.19.16 → 0.20.1

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.
Files changed (92) hide show
  1. package/README.md +11 -1
  2. package/dist/approval.d.ts +80 -54
  3. package/dist/approval.d.ts.map +1 -1
  4. package/dist/approval.js +156 -120
  5. package/dist/approval.js.map +1 -1
  6. package/dist/bootstrap.d.ts +41 -0
  7. package/dist/bootstrap.d.ts.map +1 -1
  8. package/dist/bootstrap.js +59 -2
  9. package/dist/bootstrap.js.map +1 -1
  10. package/dist/cli.js +277 -24
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compile.js +7 -0
  13. package/dist/compile.js.map +1 -1
  14. package/dist/composition.d.ts.map +1 -1
  15. package/dist/composition.js +13 -1
  16. package/dist/composition.js.map +1 -1
  17. package/dist/connectors/skill-store.d.ts.map +1 -1
  18. package/dist/connectors/skill-store.js +41 -13
  19. package/dist/connectors/skill-store.js.map +1 -1
  20. package/dist/connectors/sqlite-skill-store.d.ts.map +1 -1
  21. package/dist/connectors/sqlite-skill-store.js +21 -12
  22. package/dist/connectors/sqlite-skill-store.js.map +1 -1
  23. package/dist/connectors/types.d.ts +9 -0
  24. package/dist/connectors/types.d.ts.map +1 -1
  25. package/dist/connectors/types.js.map +1 -1
  26. package/dist/dashboard/server.d.ts +26 -0
  27. package/dist/dashboard/server.d.ts.map +1 -1
  28. package/dist/dashboard/server.js +70 -2
  29. package/dist/dashboard/server.js.map +1 -1
  30. package/dist/dashboard/spa/app.js +200 -8
  31. package/dist/dashboard/spa/index.html +2 -0
  32. package/dist/dashboard/spa/styles.css +42 -0
  33. package/dist/errors.d.ts +25 -0
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/errors.js +49 -0
  36. package/dist/errors.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/lint.d.ts.map +1 -1
  42. package/dist/lint.js +24 -7
  43. package/dist/lint.js.map +1 -1
  44. package/dist/mcp-server.d.ts +3 -0
  45. package/dist/mcp-server.d.ts.map +1 -1
  46. package/dist/mcp-server.js +19 -2
  47. package/dist/mcp-server.js.map +1 -1
  48. package/dist/parser.js +2 -2
  49. package/dist/parser.js.map +1 -1
  50. package/dist/runtime-config.d.ts +6 -0
  51. package/dist/runtime-config.d.ts.map +1 -1
  52. package/dist/runtime-config.js +8 -0
  53. package/dist/runtime-config.js.map +1 -1
  54. package/dist/runtime-env-resolver.d.ts +4 -0
  55. package/dist/runtime-env-resolver.d.ts.map +1 -1
  56. package/dist/runtime-env-resolver.js +27 -0
  57. package/dist/runtime-env-resolver.js.map +1 -1
  58. package/dist/runtime.d.ts +18 -0
  59. package/dist/runtime.d.ts.map +1 -1
  60. package/dist/runtime.js +84 -17
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/safe-path.d.ts +22 -0
  63. package/dist/safe-path.d.ts.map +1 -1
  64. package/dist/safe-path.js +72 -1
  65. package/dist/safe-path.js.map +1 -1
  66. package/dist/scheduler.d.ts +3 -0
  67. package/dist/scheduler.d.ts.map +1 -1
  68. package/dist/scheduler.js +8 -0
  69. package/dist/scheduler.js.map +1 -1
  70. package/dist/skill-catalog.d.ts.map +1 -1
  71. package/dist/skill-catalog.js +10 -2
  72. package/dist/skill-catalog.js.map +1 -1
  73. package/docs/adopter-playbook.md +75 -25
  74. package/docs/configuration.md +5 -1
  75. package/docs/connector-contract-reference.md +5 -1
  76. package/docs/language-reference.md +83 -130
  77. package/docs/sqlite-skill-store.md +5 -1
  78. package/examples/skillscripts/classify-support-ticket.skill.md +1 -1
  79. package/examples/skillscripts/data-store-roundtrip.skill.md +1 -1
  80. package/examples/skillscripts/doc-qa-with-citations.skill.md +1 -1
  81. package/examples/skillscripts/feedback-sentiment-scan.skill.md +1 -1
  82. package/examples/skillscripts/hello-world.skill.md +1 -1
  83. package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
  84. package/examples/skillscripts/morning-brief.skill.md +1 -1
  85. package/examples/skillscripts/queue-length-monitor.skill.md +1 -1
  86. package/examples/skillscripts/service-health-watch.skill.md +1 -1
  87. package/examples/skillscripts/skill-store-roundtrip.skill.md +1 -1
  88. package/examples/skillscripts/youtrack-morning-sweep.skill.md +1 -1
  89. package/package.json +1 -1
  90. package/scaffold/skills/data-store-roundtrip.skill.md +1 -1
  91. package/scaffold/skills/hello-world.skill.md +1 -1
  92. package/scaffold/skills/skill-store-roundtrip.skill.md +1 -1
@@ -1,91 +1,14 @@
1
- # Skillscript Language Reference — syntax, ops, semantics
1
+ ---
2
+ title: Language Reference
3
+ description: "Canonical spec — syntax, ops, semantics. Frontmatter, ops, filters, conditionals, triggers, output kinds, composition, error handling."
4
+ mode: wide
5
+ ---
2
6
 
3
7
  Canonical language reference for skillscript. Audience: skill authors (human + agent). Specifies what is valid syntax, what behavior to expect at compile + runtime, and what is currently pending implementation.
4
8
 
5
9
  Implementation state is cross-referenced to commit hashes; pending items mark v2/v3 work.
6
10
 
7
11
 
8
- ## Not yet implemented, but planned
9
-
10
- These are features designed or anticipated but not yet implemented in the current build. Authors should not use these forms; they will not compile.
11
-
12
- ## Control flow
13
-
14
- - **`while CONDITION:` loops** — today's iteration is `foreach IDENT in EXPR:` only. While loops are planned for ad-hoc orchestration patterns ("loop until response contains 'done'").
15
- - **Arithmetic in `$set`** — today accepts literals + `${VAR}` interpolation; no `+ - * /` operators. Planned alongside `while` for turn counters and orchestration bookkeeping.
16
-
17
- ## Pipe filter extensions
18
-
19
- - **Array aggregation primitives** — `|max`, `|min`, `|sum`, `|reduce` over arrays. Today the language tops out at "shape one record" — aggregating across an array requires `foreach` accumulator ceremony. Planned as a design question: is `foreach` the deliberate ceiling for aggregation, or do we add primitives?
20
-
21
- ## Triggers — note
22
-
23
- Trigger sources are `cron` + `event` only (see the Triggers section). There is no separate `agent-event:` / `file-watch:` / `sensor:` trigger source — anything that isn't time-based is an external adapter that POSTs to the `/event` ingress. So there's nothing pending here on the trigger axis; the entry remains only to point readers who expect those sources at the adapter-POST pattern.
24
-
25
- ## Synchronous agent exchange
26
-
27
- - **`exchange()` runtime-intrinsic op** — synchronous send + wait pattern for multi-agent conferences. Awaits adopter-substrate queue impl + AgentConnector contract grow.
28
-
29
- ## Tests
30
-
31
- - **`# Tests:` block** with `given:` / `expect:` assertions — author-authored test cases. Will land when adopter signal demands test infrastructure.
32
-
33
- ## Output kinds
34
-
35
- - **`# Output: file: <path>`** — file-output routing parses but no router exists today.
36
- - **`# Output: card:`** — depends on a substrate-side card render surface; not implemented.
37
-
38
- ## Persistent state with declared scope
39
-
40
- ```
41
- $set NAME = value scope=skill-local
42
- $set NAME = value scope=agent-global
43
- $set NAME = value scope=session
44
- ```
45
-
46
- Scopes: skill-local (persists across fires of this skill, not visible to other skills), agent-global (visible to all skills of the same agent), session (alive for the duration of the current session, cleared at session end). Backed by a configured data-records connector.
47
-
48
- ## Sensors as a read-channel category
49
-
50
- > `sensor:` is not a trigger source — to fire a skill on a sensor signal, a sensor adapter POSTs to `/event` (see Triggers). The concept below is the separate *read-channel* idea — continuous values an agent reads, distinct from triggering — which remains a future possibility if demand surfaces.
51
-
52
- Distinct from triggers. Sensors are continuous channels the agent reads but doesn't emit on. Planned syntax:
53
-
54
- ```
55
- # Sensors: presence, screen-state, voice-prosody
56
- ```
57
-
58
- Ambient refs `$(SENSOR.presence)`, `$(SENSOR.voice-prosody.affect)` for read access. Privacy-gating discipline determines when a sensor is readable.
59
-
60
- ## Time as first-class primitives
61
-
62
- Currently `$(NOW)` (wall-clock). Planned relative-time primitives:
63
-
64
- ```
65
- $(SECONDS_SINCE_LAST_USER_MESSAGE)
66
- $(MINUTES_SINCE_SESSION_START)
67
- $(SECONDS_SINCE_LAST_FIRE_OF.<skill-name>)
68
- ```
69
-
70
- Most "right time" reasoning is relative, not wall-clock.
71
-
72
- ## Other planned
73
-
74
- - **Absence-as-trigger** — `# Triggers: idle: 5m` fire-on-quiet primitive (would be a new trigger source beyond `cron`|`event`, or an adapter that POSTs on a timer)
75
- - **Time-windowed aggregation** — filter-like primitives across firings (e.g., "user has shown frustration in 3 of 5 recent turns")
76
- - **Debounce / rate-limit / coalesce** — declarative queueing policy headers
77
- - **Suppression as valid output** — explicit "fire-and-suppress" (different from `# Output: none`)
78
- - **Cross-skill pub-sub** — `# Publishes: signal.X` / `# Subscribes: signal.Y` decoupling
79
- - **Confidence/threshold gating** — `# RequiresConfidence: classifier >= 0.8` / `# RequiresThreshold:`
80
- - **Invocation-control axis** — `# Invocable-By: user | agent | trigger` (sensitive ops shouldn't leak across invocation boundaries)
81
- - **Channel/locality awareness** — `$(CHANNEL_TYPE)`, `$(CHANNEL_PRIVACY)` ambient refs for routing decisions
82
- - **Introspection primitives** — `$(PROMPT_CONTEXT.size)`, `$(SKILLS_FIRED_RECENTLY.last-1h)`, `$(SELF.confidence-trend)`
83
- - **Capability declarations** — `# Requires-Capabilities: sensors=[mic, camera], tools=[...]` (audit surface for operators)
84
-
85
- ## When the language extends, this section shrinks
86
-
87
- When any of these primitives ship, the relevant grammar moves into its canonical section (Ops reference, Variables, Triggers, etc.) and the entry here is removed. This section stays alive as a continuous staging area for the next horizon of unshipped work.
88
-
89
12
  ## Overview & language model
90
13
 
91
14
  Skillscript is a constrained domain-specific language for authoring agent workflows. A skillscript is a declarative recipe: a small program with a dependency DAG of named targets, each composed of typed operations. Skillscripts are written once and executed many times.
@@ -378,7 +301,7 @@ Closed list of language-intrinsic ops the runtime knows directly. Each is a func
378
301
  | `emit` | `emit(text="...")` | none | Append to the skill's emission stream; consumed by the configured `# Output:` delivery channel. |
379
302
  | `notify` | `notify(agent="...", message="...", [event_type=...], [correlation_id=...]) -> ACK` | optional | Mid-skill agent alert; synchronous send via configured AgentConnector. |
380
303
  | `inline` | `inline(skill="<data-skill-name>")` | none | Compile-time inline of an Approved `# Type: data` skill. Resolves at compile, records `content_hash` in provenance. |
381
- | `execute_skill` | `execute_skill(skill_name="...", inputs={...}) -> R` | optional | Composition primitive. Runtime-resolved. See Composition section. |
304
+ | `execute_skill` | `execute_skill(skill_name="...", inputs=\{...}) -> R` | optional | Composition primitive. Runtime-resolved. See Composition section. |
382
305
  | `shell` | `shell(command="...") -> R` / `shell(argv=[...]) -> R` / `shell(command="...", unsafe=true) -> R` | optional | Structural spawn (default), explicit-argv spawn (`argv=[...]`, no tokenizer), or full-shell exec (`unsafe=true`, gated by `runtime.enable_unsafe_shell`). Binary gated by the operator allowlist (see below). stdout binds. |
383
306
  | `file_read` | `file_read(path="...") -> R` | required | Read a file at `path`; binds string contents. |
384
307
  | `file_write` | `file_write(path="...", content="...")` | none | Write `content` to `path`. `mkdir -p` semantics for parent directories. Mutation-classified. |
@@ -735,12 +658,12 @@ deliver:
735
658
 
736
659
  | Class | Op | Shape | Binding |
737
660
  |---|---|---|---|
738
- | Mutation | `$set` | `$set NAME = value` (with `${VAR}` interpolation at bind) | NAME (no arrow) |
661
+ | Mutation | `$set` | `$set NAME = value` (with `$\{VAR}` interpolation at bind) | NAME (no arrow) |
739
662
  | Mutation | `$append` | `$append VAR <value>` (type-dispatched: list element / string concat) | VAR (no arrow) |
740
663
  | Runtime-intrinsic | `emit` | `emit(text="...")` or `emit(text="""...""")` for multi-line | none |
741
664
  | Runtime-intrinsic | `notify` | `notify(agent="...", [message=...], [event_type=...], [correlation_id=...]) -> ACK` | optional |
742
665
  | Runtime-intrinsic | `inline` | `inline(skill="<name>")` | none (compile-time) |
743
- | Runtime-intrinsic | `execute_skill` | `execute_skill(skill_name="...", inputs={...}) -> R` | optional |
666
+ | Runtime-intrinsic | `execute_skill` | `execute_skill(skill_name="...", inputs=\{...}) -> R` | optional |
744
667
  | Runtime-intrinsic | `shell` | `shell(command="...", [unsafe=true], [approved="..."]) [-> R] [(fallback: "...")]` or `shell(argv=[...], [approved="..."]) [-> R] [(fallback: "...")]` (mutually exclusive forms; binary allowlist applies to both) | optional |
745
668
  | Runtime-intrinsic | `file_read` | `file_read(path="...") -> R` | required |
746
669
  | Runtime-intrinsic | `file_write` | `file_write(path="...", content="...", [approved="..."])` | none |
@@ -769,13 +692,13 @@ Injected automatically at runtime; never declared by the author.
769
692
 
770
693
  | Var | Value |
771
694
  |-----|-------|
772
- | `${NOW}` | ISO-8601 timestamp at op-dispatch time |
773
- | `${USER}` | The configured user identity |
774
- | `${SESSION_CONTEXT}` | Current session-scope context (project/entity/etc., substrate-defined) |
775
- | `${TRIGGER_TYPE}` | What event fired this skill |
776
- | `${TRIGGER_PAYLOAD}` | Event-specific data |
777
- | `${EVENT.*}` | Event-payload fields populated by the trigger source |
778
- | `${ERROR_CONTEXT}` | In `# OnError:` fallback skills: type + target where failure occurred |
695
+ | `$\{NOW}` | ISO-8601 timestamp at op-dispatch time |
696
+ | `$\{USER}` | The configured user identity |
697
+ | `$\{SESSION_CONTEXT}` | Current session-scope context (project/entity/etc., substrate-defined) |
698
+ | `$\{TRIGGER_TYPE}` | What event fired this skill |
699
+ | `$\{TRIGGER_PAYLOAD}` | Event-specific data |
700
+ | `$\{EVENT.*}` | Event-payload fields populated by the trigger source |
701
+ | `$\{ERROR_CONTEXT}` | In `# OnError:` fallback skills: type + target where failure occurred |
779
702
 
780
703
  Iterator vars from `foreach` and output bindings from runtime-intrinsic / MCP-dispatch ops also pass through ambient at compile time; the runtime substitutes them per iteration / per op completion.
781
704
 
@@ -880,14 +803,14 @@ Pipe filters apply transforms to resolved variables before substitution. Syntax:
880
803
 
881
804
  | Filter | Effect | Example | Output |
882
805
  |--------|--------|---------|--------|
883
- | `url` | `encodeURIComponent(value)` | `${location|url}` for `"Asheville, NC"` | `Asheville%2C%20NC` |
884
- | `shell` | POSIX single-quote escape with outer quotes | `${arg|shell}` for `it's safe` | `'it'\''s safe'` |
885
- | `json` | `JSON.stringify(value)` | `${payload|json}` for `{k:"v"}` | `"{\"k\":\"v\"}"` |
886
- | `trim` | Whitespace trim | `${VERDICT|trim}` for `"urgent\n"` | `urgent` |
887
- | `length` | Count of items (array) or characters (string) | `${ITEMS|length}` for `["a","b","c"]` | `3` |
888
- | `contains:"X"` | Boolean: type-aware substring / element membership | `${MSG|contains:"urgent"}` for `"Yes, urgent"` | `true` |
889
- | `fallback:"X"` | Coalesce on missing/undefined/empty ref | `${VAR.missing|fallback:"-"}` | `-` |
890
- | `isodate` | Epoch seconds → ISO-8601 timestamp | `${EPOCH|isodate}` for `1779660000` | `2026-05-24T22:00:00.000Z` |
806
+ | `url` | `encodeURIComponent(value)` | `$\{location|url}` for `"Asheville, NC"` | `Asheville%2C%20NC` |
807
+ | `shell` | POSIX single-quote escape with outer quotes | `$\{arg|shell}` for `it's safe` | `'it'\''s safe'` |
808
+ | `json` | `JSON.stringify(value)` | `$\{payload|json}` for `\{k:"v"}` | `"\{\"k\":\"v\"}"` |
809
+ | `trim` | Whitespace trim | `$\{VERDICT|trim}` for `"urgent\n"` | `urgent` |
810
+ | `length` | Count of items (array) or characters (string) | `$\{ITEMS|length}` for `["a","b","c"]` | `3` |
811
+ | `contains:"X"` | Boolean: type-aware substring / element membership | `$\{MSG|contains:"urgent"}` for `"Yes, urgent"` | `true` |
812
+ | `fallback:"X"` | Coalesce on missing/undefined/empty ref | `$\{VAR.missing|fallback:"-"}` | `-` |
813
+ | `isodate` | Epoch seconds → ISO-8601 timestamp | `$\{EPOCH|isodate}` for `1779660000` | `2026-05-24T22:00:00.000Z` |
891
814
 
892
815
  ### `length` semantics
893
816
 
@@ -1501,11 +1424,11 @@ The substrate-routing ops (`$ connector.tool`, `$ data_read`, `$ llm`) and the a
1501
1424
 
1502
1425
  ## Five connector types
1503
1426
 
1504
- ### MemoryStore
1427
+ ### DataStore
1505
1428
 
1506
- Routes `$ data_read` retrieval ops. Interface: `MemoryStore.query(filters) → PortableMemory[]`.
1429
+ Routes `$ data_read` retrieval ops. Interface: `DataStore.query(filters) → PortableMemory[]`.
1507
1430
 
1508
- Implementations vary by deployment — a knowledge-substrate-backed store, a SQLite-backed store, a vector-DB-backed store, an in-memory test store. All conform to the `MemoryStore.query` contract and return `PortableMemory[]`.
1431
+ Implementations vary by deployment — a knowledge-substrate-backed store, a SQLite-backed store, a vector-DB-backed store, an in-memory test store. All conform to the `DataStore.query` contract and return `PortableMemory[]`.
1509
1432
 
1510
1433
  ### LocalModel
1511
1434
 
@@ -1621,7 +1544,7 @@ Skill records are infrastructure, not knowledge atoms — adopter impls should t
1621
1544
 
1622
1545
  All connector types expose `capabilities()` for runtime discovery. Consumers:
1623
1546
  1. `# Requires:` matching against the registered set
1624
- 2. Dynamic queries via `listMemoryStores()` / `listLocalModels()` / `listMcpConnectors()` / `listAgentConnectors()` to pick a connector for the moment
1547
+ 2. Dynamic queries via `listDataStores()` / `listLocalModels()` / `listMcpConnectors()` / `listAgentConnectors()` to pick a connector for the moment
1625
1548
  3. Authoring tools that surface the registered set
1626
1549
 
1627
1550
  ## Multi-instance by design
@@ -1630,7 +1553,7 @@ Multiple instances of the same connector type are the *normal case*, not the exc
1630
1553
 
1631
1554
  ```
1632
1555
  {
1633
- primary: MemoryStoreImplA,
1556
+ primary: DataStoreImplA,
1634
1557
  project: SqliteProjectStore,
1635
1558
  scratch: InMemoryStore
1636
1559
  }
@@ -1779,7 +1702,7 @@ interface McpDispatchCtx {
1779
1702
 
1780
1703
  Hard-coupling skills to specific substrates would make information-flow decisions infrastructural rather than skill-authored, defeating the point of skills as the agent's programming language. The connector layer is what lets the same skill body run against substrate A today and run against substrate B tomorrow without rewriting.
1781
1704
 
1782
- ## Lifecycle and status — # Status: header, six canonical states, compile + runtime enforcement
1705
+ ## Lifecycle and status — # Status: header, three canonical states (Draft / Approved / Disabled), compile + runtime enforcement
1783
1706
 
1784
1707
  Skillscripts carry an explicit lifecycle state via the `# Status:` header. The compiler and runtime enforce status — a Disabled skillscript cannot fire under any path, regardless of who invokes it.
1785
1708
 
@@ -2037,6 +1960,13 @@ default: fetch
2037
1960
 
2038
1961
  **Recursion guard.** The runtime enforces a configurable recursion-depth limit (default 10) to prevent infinite-loop composition. Exceeding the limit raises a clean structured error attributable to the offending dispatch site, not a stack overflow.
2039
1962
 
1963
+ ## Error surfacing in composition
1964
+
1965
+ Two non-obvious behaviors when reading a child's failures from the parent (both confirmed by the v1.0 runtime-semantics test battery):
1966
+
1967
+ - **Nested errors do NOT surface at the top level.** When a child op fails — e.g. the recursion-depth guard fires — the structured error nests inside the child's `R.errors`, which nests inside *its* parent's `R.errors`, and so on up the chain. It does **not** bubble to the caller's top-level `result.errors`. So a top-level `errors: []` does **not** mean nothing failed downstream. To detect a child failure, inspect the bound `${R.errors}` (and deeper), or rely on `(fallback: ...)` / `# OnError:` — those fire on the structured error regardless of nesting depth.
1968
+ - **`# Returns: R` is load-bearing for observability.** If the parent binds a child via `-> R` but does not declare `# Returns: R`, the `# Returns:` filter strips `R` from the parent's `final_vars` — and any nested child errors disappear from the parent's MCP-wire response along with it. A composition skill that wants its child's failures observable from *its own* caller must declare the binding (`# Returns: R`) explicitly. Observability of child failure is opt-in, the same way value export is.
1969
+
2040
1970
  ## Forward-reference resolution
2041
1971
 
2042
1972
  Skill references (`inline(skill=...)`, `execute_skill(skill_name=...)`) are validated at compile time but allow forward references with tier-2 advisories — making it possible to author sibling skills together (chicken-and-egg).
@@ -2152,6 +2082,7 @@ For *data skills* (skills marked `# Type: data`), the compile-time inline primit
2152
2082
 
2153
2083
  - Treat composition as a real cost. Each `execute_skill()` dispatch incurs the child's full execution time + side effects. Don't compose for trivial cases that could be inlined.
2154
2084
  - Declare `# Returns:` when a caller needs structured access to a child's variables. Leave it off for emit-only skills whose consumers read `.outputs.text` — the default-empty filter keeps scratch from propagating.
2085
+ - Want a child's failures visible to your caller? Declare the child binding in `# Returns:` — nested errors are filtered out with the binding otherwise (see Error surfacing in composition), and don't trust a top-level `errors: []`.
2155
2086
  - Pair composition with `(fallback: ...)` when the child skill might fail and the parent has a sensible degraded path.
2156
2087
  - Use mechanical mode to TestFlight any multi-skill chain before shipping it as a Headless skill on a cron trigger.
2157
2088
  - Forward references work — author sibling skills in any order, validate independently. The tier-2 warning surfaces the deferred-resolution path; runtime catches genuine misses.
@@ -2481,50 +2412,72 @@ Some features depend on others:
2481
2412
 
2482
2413
  The "Not yet implemented, but planned" section at top tracks the user-facing surface; this section preserves the design-order argument for when implementation work picks up.
2483
2414
 
2484
- ## Open spec questions unresolved language design decisions
2415
+ ## Not yet implemented, but planned
2416
+
2417
+ These are features designed or anticipated but not yet implemented in the current build. Authors should not use these forms; they will not compile.
2418
+
2419
+ ## Control flow
2485
2420
 
2486
- Questions surfaced during design that haven't been resolved. Each carries a current lean where applicable. Resolved items have moved to their canonical sections; this section tracks only what's still open.
2421
+ - **`while CONDITION:` loops** today's iteration is `foreach IDENT in EXPR:` only. While loops are planned for ad-hoc orchestration patterns ("loop until response contains 'done'").
2422
+ - **Arithmetic in `$set`** — today accepts literals + `${VAR}` interpolation; no `+ - * /` operators. Planned alongside `while` for turn counters and orchestration bookkeeping.
2487
2423
 
2488
- ## 1. Block execution model — write down the rules
2424
+ ## Pipe filter extensions
2425
+
2426
+ - **Array aggregation primitives** — `|max`, `|min`, `|sum`, `|reduce` over arrays. Today the language tops out at "shape one record" — aggregating across an array requires `foreach` accumulator ceremony. Planned as a design question: is `foreach` the deliberate ceiling for aggregation, or do we add primitives?
2427
+
2428
+ ## Triggers — note
2429
+
2430
+ Trigger sources are `cron` + `event` only (see the Triggers section). There is no separate `agent-event:` / `file-watch:` / `sensor:` trigger source — anything that isn't time-based is an external adapter that POSTs to the `/event` ingress. So there's nothing pending here on the trigger axis; the entry remains only to point readers who expect those sources at the adapter-POST pattern.
2489
2431
 
2490
- Within a target body, op ordering and variable binding conventions aren't fully written down. Specific questions:
2491
- - Can `emit()` calls precede `$` ops in the same target? (Yes; `emit()` has no dependency on subsequent ops.)
2492
- - What's the default output binding when `-> NAME` is omitted? (`${target.output}` — same as bare `target` referenced from other blocks.)
2493
- - How do cross-block references work syntactically? (`${other_target.output}` or `${VAR_BOUND_THERE}`.)
2494
2432
 
2495
- **Write a "Block execution model" subsection** in the Overview or Ops reference section. No semantic change, just documentation gap.
2433
+ ## Tests
2496
2434
 
2497
- ## 2. `else:` block visibility into the error
2435
+ - **`# Tests:` block** with `given:` / `expect:` assertions — author-authored test cases. Will land when adopter signal demands test infrastructure.
2498
2436
 
2499
- Should `${ERROR}` be an ambient ref inside `else:` blocks, populated with the error type/message? Lean: yes, same shape as `${ERROR_CONTEXT}` in `# OnError:` fallback skills. Useful for logging/telemetry skills.
2437
+ ## Output kinds
2500
2438
 
2501
- ## 3. Multiple triggersconcurrency
2439
+ - **`# Output: file: <path>`** file-output routing parses but no router exists today.
2440
+ - **`# Output: card:`** — depends on a substrate-side card render surface; not implemented.
2502
2441
 
2503
- If `cron: 0 8 * * *` and `event: user-present` both fire within seconds, does the skill run twice (independent) or get deduped? Lean: independent. Author dedups via state if needed. Affects dispatch layer.
2442
+ ## Persistent state with declared scope
2504
2443
 
2505
- ## 4. `execute_skill()` invocation vs trigger firing
2444
+ ```
2445
+ $set NAME = value scope=skill-local
2446
+ $set NAME = value scope=agent-global
2447
+ $set NAME = value scope=session
2448
+ ```
2506
2449
 
2507
- When skill A invokes skill B via `execute_skill()`, do skill B's `# Triggers:` fire? Almost certainly no `execute_skill()` is direct invocation, distinct from the trigger event surface. Worth saying explicitly.
2450
+ Scopes: skill-local (persists across fires of this skill, not visible to other skills), agent-global (visible to all skills of the same agent), session (alive for the duration of the current session, cleared at session end). Backed by a configured data-records connector.
2508
2451
 
2509
- ## 5. Output target delivery failures
2510
2452
 
2511
- If a delivery target is unreachable when the skill fires, what happens? Lean: delivery failure is its own retryable error; queue if possible, else error to caller. Worth a separate small spec section. Affects dispatch layer.
2453
+ ## Time as first-class primitives
2512
2454
 
2513
- ## 6. Skill versioning rollback UX
2455
+ Currently `$(NOW)` (wall-clock). Planned relative-time primitives:
2514
2456
 
2515
- Edits via upsert preserve history through substrate versioning (SqliteSkillStore's `skill_versions` table, FilesystemSkillStore's git history), but no first-class "rollback" affordance. Probably needs a `--version <N>` flag on the compile API or a sister tool.
2457
+ ```
2458
+ $(SECONDS_SINCE_LAST_USER_MESSAGE)
2459
+ $(MINUTES_SINCE_SESSION_START)
2460
+ $(SECONDS_SINCE_LAST_FIRE_OF.<skill-name>)
2461
+ ```
2516
2462
 
2517
- ## 7. Connector capability declarations
2463
+ Most "right time" reasoning is relative, not wall-clock.
2518
2464
 
2519
- Skills can declare required connector capabilities via `# Requires:` for var resolution. Extending this to "needs semantic search" / "needs structured-extraction model with 32K context" capabilities would be useful for the substrate-portable story. Pending design.
2465
+ ## Other planned
2520
2466
 
2521
- ## 8. Per-op timeouts
2467
+ - **Absence-as-trigger** — `# Triggers: idle: 5m` fire-on-quiet primitive (would be a new trigger source beyond `cron`|`event`, or an adapter that POSTs on a timer)
2468
+ - **Time-windowed aggregation** — filter-like primitives across firings (e.g., "user has shown frustration in 3 of 5 recent turns")
2469
+ - **Debounce / rate-limit / coalesce** — declarative queueing policy headers
2470
+ - **Suppression as valid output** — explicit "fire-and-suppress" (different from `# Output: none`)
2471
+ - **Cross-skill pub-sub** — `# Publishes: signal.X` / `# Subscribes: signal.Y` decoupling
2472
+ - **Confidence/threshold gating** — `# RequiresConfidence: classifier >= 0.8` / `# RequiresThreshold:`
2473
+ - **Invocation-control axis** — `# Invocable-By: user | agent | trigger` (sensitive ops shouldn't leak across invocation boundaries)
2474
+ - **Introspection primitives** — `$(PROMPT_CONTEXT.size)`, `$(SKILLS_FIRED_RECENTLY.last-1h)`, `$(SELF.confidence-trend)`
2522
2475
 
2523
- Hung dispatches hang the skill without explicit timeout configuration. Lean: skill-level `# Timeout:` header + per-op `timeout=N` kwarg + runtime defaults. Pending implementation; see "Not yet implemented, but planned" at top.
2476
+ ## When the language extends, this section shrinks
2524
2477
 
2525
- *(The former "file-watch path semantics" question was removed `file-watch` is no longer a trigger source; filesystem watching is now an external adapter that POSTs to the `/event` ingress, so there are no in-language file-watch path semantics to settle.)*
2478
+ When any of these primitives ship, the relevant grammar moves into its canonical section (Ops reference, Variables, Triggers, etc.) and the entry here is removed. This section stays alive as a continuous staging area for the next horizon of unshipped work.
2526
2479
 
2527
2480
  ---
2528
2481
 
2529
- *Rendered from `skillscript/skillscript-language-reference` — 2026-06-15 18:44 EDT*
2482
+ *Rendered from `skillscript/skillscript-language-reference` — 2026-06-16 15:46 EDT*
2530
2483
  *Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
@@ -1,4 +1,8 @@
1
- # SqliteSkillStore — example
1
+ ---
2
+ title: SqliteSkillStore
3
+ description: "Worked example of SkillStore against a SQLite database — schema, semantics, forking checklist."
4
+ mode: wide
5
+ ---
2
6
 
3
7
  Worked example of `SkillStore` against a SQLite database. Copy this directory into your codebase, customize per your storage needs, register with skillscript-runtime's `Registry`. This README is written for the agent implementing your adopter's connector — including the human reviewing the PR.
4
8
 
@@ -1,5 +1,5 @@
1
1
  # Skill: classify-support-ticket
2
- # Status: Approved v1:ae27d3f9
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Read an incoming support ticket and route it: severity-1 tickets get paged to ops-channel, billing tickets get tagged for finance review, everything else gets a draft reply queued for human review
5
5
  # Vars: TICKET_TEXT, TICKET_ID
@@ -1,5 +1,5 @@
1
1
  # Skill: data-store-roundtrip
2
- # Status: Approved v1:b37bc111
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Round-trips the DataStore — writes a record tagged with a per-run marker in the content, reads it back via FTS query against the same marker. The exact item count depends on substrate FTS matching strictness (strict FTS substrates return 1; substrates with looser token-match semantics may return prior runs' records that share token shapes). If this skill executes and the output line shows N ≥ 1 items returned, your DataStore substrate is wired correctly. For adopters needing deterministic counts (e.g. authoring round-trip tests), the `domain_tags` filter is the portable strict-match read path — see the adopter-playbook §"Notable things..." for the pattern.
5
5
 
@@ -1,5 +1,5 @@
1
1
  # Skill: doc-qa-with-citations
2
- # Status: Approved v1:dc6e903d
2
+ # Status: Draft
3
3
  # Description: When the user asks a question that requires retrieval over the doc set, answer with inline citations to record IDs
4
4
  # Vars: QUESTION, K=6
5
5
  # Output: text
@@ -1,5 +1,5 @@
1
1
  # Skill: feedback-sentiment-scan
2
- # Status: Approved v1:2b4868c1
2
+ # Status: Draft
3
3
  # Description: Each night, scan the previous 24h of customer feedback records, classify sentiment via the local model, and surface entries that read 'frustrated' or 'blocking' so the team sees them at start-of-day.
4
4
  # Triggers: cron: 0 3 * * *
5
5
  # Vars: SCAN_LIMIT=50
@@ -1,5 +1,5 @@
1
1
  # Skill: hello-world
2
- # Status: Approved v1:b662ccd6
2
+ # Status: Draft
3
3
  # Description: The canonical first-run example. No substrate dependencies, no compute block — pure declarative output. If this fails to execute, your install or runtime is broken; investigate before troubleshooting deeper layers. Demonstrates the template-only shape: body text IS the skill; `# Vars:` declared inputs feed the template with `--input` override.
4
4
  # Vars: WHO=world
5
5
 
@@ -2,7 +2,7 @@
2
2
  "provenance_version": "1.0",
3
3
  "language_version": "1.0",
4
4
  "compiler_version": "0.1.0-dev",
5
- "compiled_at": "2026-06-16T13:54:50.151Z",
5
+ "compiled_at": "2026-06-18T00:42:44.631Z",
6
6
  "source_skill": {
7
7
  "name": "hello-world"
8
8
  },
@@ -1,5 +1,5 @@
1
1
  # Skill: morning-brief
2
- # Status: Approved v1:1530981d
2
+ # Status: Draft
3
3
  # Description: Compose a daily morning brief from calendar, mailbox, and overnight notes when the cron trigger fires at 7am. Delivers via the agent: lifecycle hook to the receiving agent, who decides whether to surface to Slack / Discord / etc. Requires a `calendar` connector configured in connectors.json (the dotted `$ calendar.*` form). `model=qwen` is a representative LocalModel alias — adopters register one under whatever name fits (the bundled bootstrap registers `default`).
4
4
  # Vars: AGENT, BRIEF_HORIZON_HOURS=24
5
5
  # Triggers: cron: 0 7 * * *
@@ -1,5 +1,5 @@
1
1
  # Skill: queue-length-monitor
2
- # Status: Approved v1:c6bc06df
2
+ # Status: Draft
3
3
  # Description: Count pending items in a queue and alert when the count exceeds threshold. Requires `cat` on the operator's shell allowlist (default-deny: a non-allowlisted binary is refused).
4
4
  # Vars: QUEUE_PATH=/var/queue/pending.json, THRESHOLD=10
5
5
  # Triggers: cron: */5 * * * *
@@ -1,5 +1,5 @@
1
1
  # Skill: service-health-watch
2
- # Status: Approved v1:5378fb77
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Every 5 minutes check named service endpoints — if latency or status degrades, write a signal record and alert. Requires `curl` on the operator's shell allowlist (default-deny: a non-allowlisted binary is refused).
5
5
  # Vars: SERVICES=[auth-api, ledger-api, search-api], LATENCY_BUDGET_MS=400
@@ -1,5 +1,5 @@
1
1
  # Skill: skill-store-roundtrip
2
- # Status: Approved v1:55e43285
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Round-trips the SkillStore — writes a child skill, reads it back. Demonstrates the Lisp-shape primitive (skills can write skills). NOTE: in-skill `$ skill_write` lands the child as `# Status: Draft` regardless of what the body declares — to run the generated child, an authorized agent (human via dashboard, or MCP-direct) reviews + promotes via the outside-MCP `skill_status` tool. The Draft-default gate keeps autonomously-written skills out of the immediate execution loop.
5
5
 
@@ -1,5 +1,5 @@
1
1
  # Skill: youtrack-morning-sweep
2
- # Status: Approved v1:d4c872d9
2
+ # Status: Draft
3
3
  # Description: Pull recent YouTrack issues via a configured RemoteMcpConnector + dotted field access on parsed JSON + foreach over the issues array. Requires a `youtrack` connector configured in connectors.json (RemoteMcpConnector class, mcp-remote bridge, newline framing) with allowed_tools including search_issues + get_current_user.
4
4
 
5
5
  fetch_me:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscript-runtime",
3
- "version": "0.19.16",
3
+ "version": "0.20.1",
4
4
  "description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Scott Shwarts <scotts@pobox.com>",
@@ -1,5 +1,5 @@
1
1
  # Skill: data-store-roundtrip
2
- # Status: Approved v1:b37bc111
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Round-trips the DataStore — writes a record tagged with a per-run marker in the content, reads it back via FTS query against the same marker. The exact item count depends on substrate FTS matching strictness (strict FTS substrates return 1; substrates with looser token-match semantics may return prior runs' records that share token shapes). If this skill executes and the output line shows N ≥ 1 items returned, your DataStore substrate is wired correctly. For adopters needing deterministic counts (e.g. authoring round-trip tests), the `domain_tags` filter is the portable strict-match read path — see the adopter-playbook §"Notable things..." for the pattern.
5
5
 
@@ -1,5 +1,5 @@
1
1
  # Skill: hello-world
2
- # Status: Approved v1:b662ccd6
2
+ # Status: Draft
3
3
  # Description: The canonical first-run example. No substrate dependencies, no compute block — pure declarative output. If this fails to execute, your install or runtime is broken; investigate before troubleshooting deeper layers. Demonstrates the template-only shape: body text IS the skill; `# Vars:` declared inputs feed the template with `--input` override.
4
4
  # Vars: WHO=world
5
5
 
@@ -1,5 +1,5 @@
1
1
  # Skill: skill-store-roundtrip
2
- # Status: Approved v1:55e43285
2
+ # Status: Draft
3
3
  # Autonomous: true
4
4
  # Description: Round-trips the SkillStore — writes a child skill, reads it back. Demonstrates the Lisp-shape primitive (skills can write skills). NOTE: in-skill `$ skill_write` lands the child as `# Status: Draft` regardless of what the body declares — to run the generated child, an authorized agent (human via dashboard, or MCP-direct) reviews + promotes via the outside-MCP `skill_status` tool. The Draft-default gate keeps autonomously-written skills out of the immediate execution loop.
5
5