skillscript-runtime 0.13.8 → 0.14.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 (117) hide show
  1. package/README.md +12 -12
  2. package/dist/bootstrap.d.ts +13 -13
  3. package/dist/bootstrap.d.ts.map +1 -1
  4. package/dist/bootstrap.js +31 -31
  5. package/dist/bootstrap.js.map +1 -1
  6. package/dist/cli.js +6 -6
  7. package/dist/cli.js.map +1 -1
  8. package/dist/compile.js +2 -2
  9. package/dist/compile.js.map +1 -1
  10. package/dist/connectors/config.d.ts +4 -4
  11. package/dist/connectors/config.d.ts.map +1 -1
  12. package/dist/connectors/config.js +7 -7
  13. package/dist/connectors/config.js.map +1 -1
  14. package/dist/connectors/data-store-mcp.d.ts +29 -0
  15. package/dist/connectors/data-store-mcp.d.ts.map +1 -0
  16. package/dist/connectors/{memory-store-mcp.js → data-store-mcp.js} +56 -31
  17. package/dist/connectors/data-store-mcp.js.map +1 -0
  18. package/dist/connectors/{memory-store.d.ts → data-store.d.ts} +12 -12
  19. package/dist/connectors/data-store.d.ts.map +1 -0
  20. package/dist/connectors/{memory-store.js → data-store.js} +7 -7
  21. package/dist/connectors/data-store.js.map +1 -0
  22. package/dist/connectors/filter-enforcement.d.ts +18 -0
  23. package/dist/connectors/filter-enforcement.d.ts.map +1 -0
  24. package/dist/connectors/filter-enforcement.js +57 -0
  25. package/dist/connectors/filter-enforcement.js.map +1 -0
  26. package/dist/connectors/index.d.ts +5 -5
  27. package/dist/connectors/index.d.ts.map +1 -1
  28. package/dist/connectors/index.js +7 -7
  29. package/dist/connectors/index.js.map +1 -1
  30. package/dist/connectors/local-model-mcp.js +1 -1
  31. package/dist/connectors/local-model-mcp.js.map +1 -1
  32. package/dist/connectors/registry.d.ts +10 -10
  33. package/dist/connectors/registry.d.ts.map +1 -1
  34. package/dist/connectors/registry.js +11 -11
  35. package/dist/connectors/registry.js.map +1 -1
  36. package/dist/connectors/sqlite-skill-store.js +1 -1
  37. package/dist/connectors/sqlite-skill-store.js.map +1 -1
  38. package/dist/connectors/types.d.ts +36 -23
  39. package/dist/connectors/types.d.ts.map +1 -1
  40. package/dist/connectors/types.js +2 -2
  41. package/dist/connectors/types.js.map +1 -1
  42. package/dist/dashboard/spa/app.js +2 -2
  43. package/dist/errors.d.ts +43 -1
  44. package/dist/errors.d.ts.map +1 -1
  45. package/dist/errors.js +70 -4
  46. package/dist/errors.js.map +1 -1
  47. package/dist/help-content.js +27 -27
  48. package/dist/help-content.js.map +1 -1
  49. package/dist/index.d.ts +5 -5
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +4 -4
  52. package/dist/index.js.map +1 -1
  53. package/dist/lint.d.ts.map +1 -1
  54. package/dist/lint.js +48 -56
  55. package/dist/lint.js.map +1 -1
  56. package/dist/mcp-server.d.ts +1 -1
  57. package/dist/mcp-server.d.ts.map +1 -1
  58. package/dist/mcp-server.js +19 -16
  59. package/dist/mcp-server.js.map +1 -1
  60. package/dist/mutation-gate.d.ts +61 -0
  61. package/dist/mutation-gate.d.ts.map +1 -0
  62. package/dist/mutation-gate.js +89 -0
  63. package/dist/mutation-gate.js.map +1 -0
  64. package/dist/runtime-config.d.ts +2 -2
  65. package/dist/runtime-config.d.ts.map +1 -1
  66. package/dist/runtime-config.js +1 -1
  67. package/dist/runtime-config.js.map +1 -1
  68. package/dist/runtime.d.ts.map +1 -1
  69. package/dist/runtime.js +59 -17
  70. package/dist/runtime.js.map +1 -1
  71. package/dist/skill-manager.js +1 -1
  72. package/dist/skill-manager.js.map +1 -1
  73. package/dist/testing/conformance.d.ts +8 -8
  74. package/dist/testing/conformance.d.ts.map +1 -1
  75. package/dist/testing/conformance.js +8 -8
  76. package/dist/testing/conformance.js.map +1 -1
  77. package/dist/testing/index.d.ts +2 -2
  78. package/dist/testing/index.d.ts.map +1 -1
  79. package/dist/testing/index.js +1 -1
  80. package/dist/testing/index.js.map +1 -1
  81. package/dist/trace.d.ts +3 -3
  82. package/docs/adopter-playbook.md +85 -41
  83. package/docs/configuration.md +8 -8
  84. package/docs/connector-contract-reference.md +9 -9
  85. package/docs/language-reference.md +38 -63
  86. package/docs/sqlite-skill-store.md +2 -2
  87. package/examples/README.md +5 -5
  88. package/examples/connectors/{MemoryStoreTemplate/MemoryStoreTemplate.ts → DataStoreTemplate/DataStoreTemplate.ts} +56 -43
  89. package/examples/connectors/{MemoryStoreTemplate → DataStoreTemplate}/README.md +39 -39
  90. package/examples/connectors/LocalModelTemplate/README.md +1 -1
  91. package/examples/connectors/McpConnectorTemplate/McpConnectorTemplate.ts +2 -2
  92. package/examples/connectors/McpConnectorTemplate/README.md +5 -5
  93. package/examples/connectors/README.md +3 -3
  94. package/examples/connectors/SkillStoreTemplate/README.md +1 -1
  95. package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +1 -1
  96. package/examples/custom-bootstrap.example.ts +15 -8
  97. package/examples/onboarding-scaffold/README.md +9 -10
  98. package/examples/onboarding-scaffold/bootstrap.ts +18 -13
  99. package/examples/onboarding-scaffold/connectors.json +1 -1
  100. package/examples/onboarding-scaffold/{file-memory-store.ts → file-data-store.ts} +34 -23
  101. package/examples/programmatic-trace-demo.mjs +1 -1
  102. package/examples/skillscripts/classify-support-ticket.skill.md +9 -8
  103. package/examples/skillscripts/cut-release-tag.skill.md +6 -6
  104. package/examples/skillscripts/doc-qa-with-citations.skill.md +3 -3
  105. package/examples/skillscripts/feedback-sentiment-scan.skill.md +9 -8
  106. package/examples/skillscripts/hello.skill.provenance.json +1 -1
  107. package/examples/skillscripts/morning-brief.skill.md +3 -3
  108. package/examples/skillscripts/service-health-watch.skill.md +5 -4
  109. package/package.json +1 -1
  110. package/scaffold/config.toml +6 -4
  111. package/scaffold/connectors.json +3 -3
  112. package/dist/connectors/memory-store-mcp.d.ts +0 -20
  113. package/dist/connectors/memory-store-mcp.d.ts.map +0 -1
  114. package/dist/connectors/memory-store-mcp.js.map +0 -1
  115. package/dist/connectors/memory-store.d.ts.map +0 -1
  116. package/dist/connectors/memory-store.js.map +0 -1
  117. /package/examples/onboarding-scaffold/{memory.example.json → data.example.json} +0 -0
@@ -4,9 +4,6 @@ Canonical language reference for skillscript. Audience: skill authors (human + a
4
4
 
5
5
  Implementation state is cross-referenced to commit hashes; pending items mark v2/v3 work.
6
6
 
7
- Companion docs under the Skillscript project anchor:
8
- - `skillscript-prd` — product positioning, value prop, roadmap
9
- - `skillscript-erd` — engineering requirements, system architecture, runtime mechanics
10
7
 
11
8
  ## Not yet implemented, but planned
12
9
 
@@ -103,7 +100,7 @@ Skillscript is a constrained domain-specific language for authoring agent workfl
103
100
  Every skill follows the same shape:
104
101
 
105
102
  1. **Trigger** — what fires the skill: cron, command, session-start, agent-event, file-watch, webhook, etc.
106
- 2. **Process** — pull data (MCP / memory / file), classify or compose via sub-LLM + iteration, build the deliverable.
103
+ 2. **Process** — pull data (MCP / data store / file), classify or compose via sub-LLM + iteration, build the deliverable.
107
104
  3. **Deliver** — emit the result via one or more delivery channels (see below).
108
105
 
109
106
  Skillscript's job is to express this pipeline declaratively. When there is an agent above the skill, the agent's job is to act on the delivered artifact. When there isn't (autonomous fires), the delivery channel IS the outcome.
@@ -128,9 +125,9 @@ A skill delivers its work via one or more of three channels. Delivery channel is
128
125
  |---|---|---|
129
126
  | **Augmenting (context to agent)** | `emit(text="...")` + `# Output: agent: <name>` | Skill output is augment-kind payload for the receiving agent's next turn; joined emit stream becomes the delivered context. Pattern: agent-augmenting skills (briefing skills, session-start prepared context). |
130
127
  | **Template (playbook to agent)** | `emit(text="...")` + `# Output: template: <name>` | Skill output is a template-kind payload (recipe/playbook) the receiving agent executes. Pattern: instructional skills, reusable recipes. |
131
- | **Memory handoff** | `$ memory_write content="..." recipients=[<agent>] -> R` | Skill writes a memory the target agent picks up via mailbox at next session. Pattern: async carrier skills, autonomous fires that hand off to a future session. |
128
+ | **Data handoff** | `$ data_write content="..." recipients=[<agent>] -> R` | Skill writes data the target agent picks up via mailbox at next session. Pattern: async carrier skills, autonomous fires that hand off to a future session. |
132
129
 
133
- A single skill can use any combination. An autonomous cron-fired sweep might write a memory to one agent AND emit augment-kind context to another. The combinations are unconstrained — the per-op gating model governs which mutating ops are authorized, not which channels a skill uses.
130
+ A single skill can use any combination. An autonomous cron-fired sweep might write data to one agent AND emit augment-kind context to another. The combinations are unconstrained — the per-op gating model governs which mutating ops are authorized, not which channels a skill uses.
134
131
 
135
132
  ## Three op classes
136
133
 
@@ -148,19 +145,19 @@ Full op catalog and per-op semantics in Ops Reference.
148
145
 
149
146
  ## Substrate portability
150
147
 
151
- The language doesn't privilege any backend. `$ llm`, `$ memory`, `$ ticketing_search` are not language built-ins — they're connector names resolved at runtime through the registered MCP connector instances. The same skill source runs against any conforming substrate.
148
+ The language doesn't privilege any backend. `$ llm`, `$ data_read`, `$ ticketing_search` are not language built-ins — they're connector names resolved at runtime through the registered MCP connector instances. The same skill source runs against any conforming substrate.
152
149
 
153
150
  The runtime ships bundled bridges for two common patterns:
154
151
 
155
152
  - `$ llm` routes through whichever LocalModel is wired (via `substrate.local_model` in `connectors.json`)
156
- - `$ memory` / `$ memory_write` route through whichever MemoryStore is wired (via `substrate.memory_store`)
153
+ - `$ data_read` / `$ data_write` route through whichever DataStore is wired (via `substrate.data_store`)
157
154
 
158
155
  Adopters wire OpenAI instead of Ollama, Pinecone instead of SQLite, etc. Configuration lives outside the skill body; the language remains agnostic.
159
156
 
160
157
  | Connector slot | Adopter A wires | Adopter B wires |
161
158
  |---|---|---|
162
159
  | `llm` | LocalModel: Ollama (default) | LocalModel: OpenAI |
163
- | `memory` / `memory_write` | MemoryStore: SqliteMemoryStore (default) | MemoryStore: Pinecone |
160
+ | `data_read` / `data_write` | DataStore: SqliteDataStore (default) | DataStore: Pinecone |
164
161
  | `ticketing_search` | YouTrack MCP | Jira MCP |
165
162
 
166
163
  Substrate config syntax + the three-form configuration shape lives in the adopter playbook, not in this reference. Skill authors don't typically need to touch it — they author against the canonical `$ tool` surfaces and adopters wire whatever's underneath.
@@ -201,7 +198,7 @@ The joined `emit()` stream becomes the augment-kind payload delivered to the on-
201
198
  2. **Targets** — named blocks of typed ops, optionally with `needs:` dependencies
202
199
  3. **`default:`** — names the goal target the runtime walks toward
203
200
 
204
- Other delivery channels for the same shape: swap `# Output: agent: oncall` + the `emit()` calls for `$ memory_write content="..." recipients=["oncall"] approved="cron-fired" -> R` (one summary memory handoff per fire) or `file_write(path="/var/log/showstoppers-${EVENT.fired_at_unix}.md", content="...", approved="cron-fired")` (one file deliverable per fire).
201
+ Other delivery channels for the same shape: swap `# Output: agent: oncall` + the `emit()` calls for `$ data_write content="..." recipients=["oncall"] approved="cron-fired" -> R` (one summary data handoff per fire) or `file_write(path="/var/log/showstoppers-${EVENT.fired_at_unix}.md", content="...", approved="cron-fired")` (one file deliverable per fire).
205
202
 
206
203
  ## Lexical conventions
207
204
 
@@ -476,14 +473,14 @@ Both forms are first-class. Neither is canonical; choose by what makes the call
476
473
  ```
477
474
  $ ticketing_search query="project:INFRA" -> R
478
475
  $ llm prompt="${INPUT}" -> V
479
- $ memory mode=fts query="..." limit=5 -> M
476
+ $ data_read mode=fts query="..." limit=5 -> M
480
477
  ```
481
478
 
482
479
  **Dotted-prefix dispatch** is the explicit-routing escape hatch — useful when multiple connectors expose tools with overlapping names, or when an adopter wants the connector identity visible at the call site for audit clarity:
483
480
 
484
481
  ```
485
482
  $ ticketing.search query="project:INFRA" -> R
486
- $ memory.query_memories query="..." -> M
483
+ $ data_read.query query="..." -> M
487
484
  ```
488
485
 
489
486
  Parser rule: the text before the dot is the connector name (must match an entry in `connectors.json`); the rest is the tool + args.
@@ -493,13 +490,13 @@ Parser rule: the text before the dot is the connector name (must match an entry
493
490
  ```
494
491
  $ ticketing_search query="project:INFRA state:Open" limit=20 -> ISSUES
495
492
  $ llm prompt="Classify: ${INPUT}" -> VERDICT
496
- $ memory mode=fts query="${TOPIC}" limit=5 -> RESULTS
497
- $ memory_write content="${SUMMARY}" recipients=["oncall"] approved="morning roundup, 2026-05-25" -> ACK
493
+ $ data_read mode=fts query="${TOPIC}" limit=5 -> RESULTS
494
+ $ data_write content="${SUMMARY}" recipients=["oncall"] approved="morning roundup, 2026-05-25" -> ACK
498
495
  ```
499
496
 
500
497
  Tool args are unconstrained `key=value` pairs — the connector forwards them to the underlying MCP tool. If a dispatched call returns `isError: true`, the executor throws via `makeOpError`, which routes through `else:` / `# OnError:` machinery. The inner tool's error text is preserved in `result.errors[]`.
501
498
 
502
- **Substrate-neutrality.** Connector names like `$ llm`, `$ memory`, `$ ticketing_search` are NOT reserved or built-in — they're whatever the adopter declares in `connectors.json` (substrate config). Bridges for `$ llm` and `$ memory` / `$ memory_write` auto-wire only when the adopter's substrate config sets `substrate.local_model` / `substrate.memory_store` respectively. See the adopter playbook for the full substrate config reference.
499
+ **Substrate-neutrality.** Connector names like `$ llm`, `$ data_read`, `$ ticketing_search` are NOT reserved or built-in — they're whatever the adopter declares in `connectors.json` (substrate config). Bridges for `$ llm` and `$ data_read` / `$ data_write` auto-wire only when the adopter's substrate config sets `substrate.local_model` / `substrate.data_store` respectively. See the adopter playbook for the full substrate config reference.
503
500
 
504
501
  **Unknown connector** → tier-1 `unknown-connector` lint with the list of wired connector names.
505
502
 
@@ -513,7 +510,7 @@ Mutation ops require an authorization signal. The signal is per-op, not a mode b
513
510
 
514
511
  **Mutation-classified ops:**
515
512
  - `file_write(...)` (runtime-intrinsic)
516
- - `$ memory_write ...` and any MCP connector entry declared `"mutating": true` in `connectors.json`
513
+ - `$ data_write ...` and any MCP connector entry declared `"mutating": true` in `connectors.json`
517
514
  - `shell(command=..., unsafe=true)` (always mutation-classified)
518
515
  - `shell(command=...)` with destructive verb (rm, mv, dd, mkfs, etc. — heuristic list)
519
516
  - `$ <tool>` matching the mutating-verb regex
@@ -537,7 +534,7 @@ Mutation ops require an authorization signal. The signal is per-op, not a mode b
537
534
 
538
535
  deliver:
539
536
  file_write(path="/tmp/sweep.md", content="${REPORT}") # no approved= needed
540
- $ memory_write content="${REPORT}" recipients=["oncall"] # no approved= needed
537
+ $ data_write content="${REPORT}" recipients=["oncall"] # no approved= needed
541
538
  ```
542
539
 
543
540
  ```
@@ -577,35 +574,15 @@ flush:
577
574
  | Runtime-intrinsic | `file_write` | `file_write(path="...", content="...", [approved="..."])` | none |
578
575
  | External MCP | `$ <connector>` | `$ <name>[.<tool>] kwarg=value, ... [-> R]` | optional |
579
576
 
580
- ---
581
-
582
- ## Legacy syntax (deprecated, grace period)
583
-
584
- These symbol-forms shipped in earlier versions and still compile during the grace period with tier-2 `deprecated-symbol-op` warnings. New skills should use the canonical forms.
585
-
586
- | Deprecated | Canonical replacement |
587
- |---|---|
588
- | `~ prompt="..."` | `$ llm prompt="..."` |
589
- | `> mode=... query=...` | `$ memory mode=... query=...` |
590
- | `@ <command>` | `shell(command="...")` |
591
- | `@ unsafe <command>` | `shell(command="...", unsafe=true)` |
592
- | `! <text>` | `emit(text="<text>")` |
593
- | `?? "<prompt>" -> R` | `ask(prompt="<prompt>") -> R` |
594
- | `& <data-skill-name>` | `inline(skill="<data-skill-name>")` |
595
- | `$(VAR)` (legacy substitution) | `${VAR}` |
596
- | `(approved: "reason")` trailer | `approved="reason"` kwarg |
597
-
598
- The symbol-per-op design was deprecated when verb-word ops in function-call shape proved more author-friendly (training-corpus alignment + human-reviewability). Removal lands in a future version; until then, the canonical surface is the recommended form.
599
-
600
577
  ## Variable resolution — ${VAR} canonical, substitution + ambient refs + # Requires: cascade
601
578
 
602
- Skillscript supports four tiers of variables, each with distinct resolution timing and scope. Substitution uses **`${VAR}` as the canonical form**. The legacy `$(VAR)` form (parentheses) continues to compile during the grace period; see Ops Reference legacy syntax section for the deprecation map.
579
+ Skillscript supports four tiers of variables, each with distinct resolution timing and scope. Substitution uses **`${VAR}` as the canonical form**.
603
580
 
604
581
  ## Substitution syntax — `${VAR}` canonical
605
582
 
606
583
  ```
607
584
  emit(text="Hello, ${USER.login}!")
608
- $ memory mode=fts query="${TOPIC}" limit=5 -> R
585
+ $ data_read mode=fts query="${TOPIC}" limit=5 -> R
609
586
  $set REPORT = "Triage for ${PROJECT} (${ISSUES|length} open):\n"
610
587
  ```
611
588
 
@@ -638,7 +615,7 @@ For cron and session triggers, the scheduler injects time-offset ambient fields
638
615
 
639
616
  These let skill bodies compute `expires_at` and similar bounded-lifetime values without arithmetic in op kwargs. For ISO-formatted rendering of any epoch value, see the `|isodate` filter.
640
617
 
641
- Additional ambient refs may be injected based on connector configuration (e.g., a vault-backed memory connector may expose `${VAULT_ROOT}`). Connectors section documents which ambient refs each connector contributes.
618
+ Additional ambient refs may be injected based on connector configuration. Connector documentation specifies which ambient refs each connector contributes.
642
619
 
643
620
  ## Tier 2: Input
644
621
 
@@ -662,18 +639,16 @@ Bracketed list literals supported (`# Vars: TAGS=[a, b, c]`).
662
639
 
663
640
  ## Tier 4: Local
664
641
 
665
- Bound to a previous target's output mid-execution. Two forms:
642
+ Bound to a previous target's output mid-execution. Three forms:
666
643
  - `${target.output}` — the bound output of a target
667
644
  - `${VAR}` — an explicit `-> VAR` binding from any op
668
- - `${target.output.field}` or `${MEMORY.field}` — dotted field access into structured output
645
+ - `${VAR.field}` / `${VAR.nested.field}` — dotted field access into structured output
669
646
 
670
- **Field access resolution tiers** for `${MEMORY.field}`:
671
- 1. Core `PortableMemory` fields (id, summary, detail, score)
672
- 2. Curated substrate subset (thread_status, pinned, confidence, domain_tags, payload_type, knowledge_type, recipients, expires_at, created_at, agent_id, vault)
673
- 3. `metadata.X` for everything else
674
- 4. Ambient passthrough as literal `${MEMORY.field}` if unresolved
647
+ The exact field set available on a bound variable depends on what the wired connector returns. Field access resolves against the returned object's structure. If a field doesn't resolve, the substitution errors unless `|fallback:"default"` is supplied (see Pipe filters for full `|fallback:` semantics):
675
648
 
676
- **Missing-field opt-out:** `${MEMORY.field|fallback:"-"}` coalesces to the literal when the field doesn't resolve. See Pipe filters for the full `|fallback:` semantics.
649
+ ```
650
+ emit(text="Assignee: ${ISSUE.customFields.Assignee|fallback:\"unassigned\"}")
651
+ ```
677
652
 
678
653
  ## Resolution order
679
654
 
@@ -751,7 +726,7 @@ Strings that hold JSON arrays get the same tolerance as `in`/`not in` RHS: if th
751
726
  Pairs naturally with the numeric comparison operators (see Conditionals section):
752
727
 
753
728
  ```
754
- $ memory mode=fts query="urgent" -> ITEMS
729
+ $ data_read mode=fts query="urgent" -> ITEMS
755
730
  if ${ITEMS|length} > 5:
756
731
  emit(text="Mailbox is getting crowded")
757
732
  ```
@@ -901,7 +876,7 @@ The ref-vs-ref form is the canonical change-detection pattern. Both sides resolv
901
876
  if ${M.id|trim} in ${SEEN}:
902
877
  emit(text="already processed")
903
878
  elif ${M.id} not in ${SEEN}:
904
- $ memory_write content="..." approved="dedup" -> R
879
+ $ data_write content="..." approved="dedup" -> R
905
880
  ```
906
881
 
907
882
  Both sides are explicit refs. RHS must resolve to an array at runtime; clean error otherwise. LHS-undefined evaluates to `false` for both polarities. Optional filter on LHS.
@@ -909,9 +884,9 @@ Both sides are explicit refs. RHS must resolve to an array at runtime; clean err
909
884
  **JSON-string tolerance on RHS**: if the RHS resolves to a *string* that successfully JSON-parses to an array, the parsed array is used. This accommodates the canonical pattern where the array comes from a `$ llm` call that prompted for JSON output:
910
885
 
911
886
  ```
912
- $ llm prompt="List the URGENT memory IDs as a JSON array of strings. Items: ${M|json}" -> SEEN
887
+ $ llm prompt="List the URGENT item IDs as a JSON array of strings. Items: ${M|json}" -> SEEN
913
888
 
914
- foreach M in ${MEMORIES}:
889
+ foreach M in ${ITEMS}:
915
890
  if ${M.id} in ${SEEN}:
916
891
  emit(text="flagged urgent")
917
892
  ```
@@ -932,7 +907,7 @@ elif ${COUNT} <= 0:
932
907
  Filters and dotted-field access work on either side, same as equality. The `|length` filter (see Pipe filters section) is the canonical companion — `${LIST|length} > 5` is the natural "more than five items" pattern:
933
908
 
934
909
  ```
935
- $ memory mode=fts query="urgent" -> ITEMS
910
+ $ data_read mode=fts query="urgent" -> ITEMS
936
911
  if ${ITEMS|length} > 5:
937
912
  emit(text="Mailbox is getting crowded")
938
913
  ```
@@ -972,7 +947,7 @@ classify:
972
947
 
973
948
  ```
974
949
  mailbox_check:
975
- $ memory mode=fts query="addressed:perry" limit=10 -> MAILBOX
950
+ $ data_read mode=fts query="addressed:perry" limit=10 -> MAILBOX
976
951
  if not ${MAILBOX}:
977
952
  emit(text="empty mailbox today")
978
953
  elif ${MAILBOX|length} > 5:
@@ -1019,12 +994,12 @@ Both can coexist in the same target.
1019
994
  foreach M in ${RESULTS}:
1020
995
  emit(text="Processing ${M.id} — ${M.summary}")
1021
996
  if ${M.id|trim} not in ${SEEN}:
1022
- $ memory_write content="${M.summary}" approved="dedup" -> ACK
997
+ $ data_write content="${M.summary}" approved="dedup" -> ACK
1023
998
  ```
1024
999
 
1025
1000
  ### Iterator vars
1026
1001
 
1027
- `${M}` and `${M.field}` pass through ambient at compile; runtime substitutes per iteration. Dotted field access against `PortableMemory` shape applies (core fields curated subset metadata). Indexed access (`${LIST.0}`, `${LIST.0.id}`) also works on bound results.
1002
+ `${M}` and `${M.field}` pass through ambient at compile; runtime substitutes per iteration. Dotted field access resolves against whatever structure the connector returned per item the exact field set depends on the connector. Indexed access (`${LIST.0}`, `${LIST.0.id}`) also works on bound results.
1028
1003
 
1029
1004
  ### Loop-local scope (and the accumulator exception)
1030
1005
 
@@ -1369,7 +1344,7 @@ Runs if any op in the target's primary body errors. Local to the failing target.
1369
1344
 
1370
1345
  ```
1371
1346
  fetch:
1372
- $ memory mode=fts query=${TOPIC} limit=5 -> RESULT
1347
+ $ data_read mode=fts query=${TOPIC} limit=5 -> RESULT
1373
1348
  else:
1374
1349
  emit(text="retrieval failed, falling back to empty result")
1375
1350
  $set RESULT = ""
@@ -1412,7 +1387,7 @@ Inline fallback declared on the op line. Used when the call fails or returns emp
1412
1387
 
1413
1388
  ```
1414
1389
  weather:
1415
- $ memory mode=fts query="weather ${LOCATION}" limit=1 -> CURRENT (fallback: "weather unavailable")
1390
+ $ data_read mode=fts query="weather ${LOCATION}" limit=1 -> CURRENT (fallback: "weather unavailable")
1416
1391
  $ llm prompt="Summarize: ${CURRENT}" -> SUMMARY (fallback: "summary unavailable")
1417
1392
  $ slack.post channel=${CHANNEL} text=${SUMMARY} (fallback: "post failed silently") -> ACK
1418
1393
  ```
@@ -1422,7 +1397,7 @@ Same pattern as the `# Requires:` cascade's `(fallback: ...)` syntax — consist
1422
1397
  **Fallback value parsing.** Permissive: bare identifiers, quoted strings, and bracketed array literals all accepted. Matches the `# Requires:` cascade convention.
1423
1398
 
1424
1399
  ```
1425
- $ memory mode=fts query="..." -> RESULTS (fallback: []) # array literal
1400
+ $ data_read mode=fts query="..." -> RESULTS (fallback: []) # array literal
1426
1401
  $ llm prompt="..." -> VERDICT (fallback: unknown) # bare identifier
1427
1402
  $ slack.post text="..." -> ACK (fallback: "post failed") # quoted string
1428
1403
  ```
@@ -1728,7 +1703,7 @@ Normal `prompt` / `prose` compilation ignores the `# Tests:` section entirely
1728
1703
 
1729
1704
  ### Runtime assertion sandboxing
1730
1705
 
1731
- `# Tests:` cases that exercise runtime behavior (memory writes, shell ops, LocalModel calls) need a sandbox so they don't pollute production data. Two approaches:
1706
+ `# Tests:` cases that exercise runtime behavior (data writes, shell ops, LocalModel calls) need a sandbox so they don't pollute production data. Two approaches:
1732
1707
  - Scratch DB / scratch connector overrides for tests
1733
1708
  - Skip-and-warn for non-deterministic ops, only assert deterministic compile-time properties
1734
1709
 
@@ -1832,7 +1807,7 @@ $set NAME = value scope=session
1832
1807
 
1833
1808
  Backed by a configured data-records connector (the same surface `# Requires:` reads from) with conventionally-namespaced keys (e.g., `state:skill-local:<skill-name>:<key>`).
1834
1809
 
1835
- **Rationale:** Most interesting skills need memory across firings — change-detection, windowing, dedup-against-recent. Without lifecycle, every skill rebuilds state tracking via raw memory-write / memory-query calls.
1810
+ **Rationale:** Most interesting skills need persistent state across firings — change-detection, windowing, dedup-against-recent. Without lifecycle, every skill rebuilds state tracking via raw `$ data_write` / `$ data_read` calls.
1836
1811
 
1837
1812
  ## Cross-skill pub-sub
1838
1813
 
@@ -1905,7 +1880,7 @@ ${SELF.confidence-trend}
1905
1880
  Skill declares its required surfaces:
1906
1881
 
1907
1882
  ```
1908
- # Requires-Capabilities: sensors=[mic, camera], tools=[memorystore.write, slack.post]
1883
+ # Requires-Capabilities: sensors=[mic, camera], tools=[datastore.write, slack.post]
1909
1884
  # Requires-Privacy: private-channel-only
1910
1885
  ```
1911
1886
 
@@ -1968,5 +1943,5 @@ Hung dispatches hang the skill without explicit timeout configuration. Lean: ski
1968
1943
 
1969
1944
  ---
1970
1945
 
1971
- *Rendered from `skillscript/skillscript-language-reference` — 2026-05-29 10:20 EDT*
1946
+ *Rendered from `skillscript/skillscript-language-reference` — 2026-05-30 09:52 EDT*
1972
1947
  *Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
@@ -42,7 +42,7 @@ const store = registry.getSkillStore("primary");
42
42
  await store.store("morning-status", `# Skill: morning-status
43
43
  # Status: Approved
44
44
  t:
45
- ! report status
45
+ emit(text="report status")
46
46
  default: t
47
47
  `);
48
48
  ```
@@ -159,7 +159,7 @@ registerApprovalFn("v2", (body) => hmacSha256(SECRET, body));
159
159
  When forking into your codebase:
160
160
 
161
161
  1. Rename the class (e.g., `PostgresSkillStore`, `AmpSkillStore`)
162
- 2. Replace the SQL with your substrate's API (HTTP, MemoryStore, vector DB, etc.)
162
+ 2. Replace the SQL with your substrate's API (HTTP, DataStore, vector DB, etc.)
163
163
  3. Update `staticCapabilities()` to match what your substrate actually supports — drop `supports_versioning` if you can't track history, drop `supports_tag_filter` if querying tags isn't tractable
164
164
  4. Update `manifest()` to describe your substrate (`kind: "amp"` or whatever)
165
165
  5. Tests: copy `tests/SqliteSkillStore.test.ts` as a starting point + run the conformance suite (`SkillStoreConformance.buildTests()` from `skillscript-runtime/testing`)
@@ -9,9 +9,9 @@ For full language semantics, see [`../docs/language-reference.md`](../docs/langu
9
9
  | [`hello.skill.md`](./skillscripts/hello.skill.md) | Three-command first run; `! emit`; default target; `# Vars:` declared inputs |
10
10
  | [`morning-brief.skill.md`](./skillscripts/morning-brief.skill.md) | Multi-target with `needs:`; cron-fired with `EVENT.fired_at_*`; `# OnError:` fallback; dual `# Output:` (slack + prompt-context); `# Requires:` user-var cascade with fallback; LocalModel + retrieval composition |
11
11
  | [`doc-qa-with-citations.skill.md`](./skillscripts/doc-qa-with-citations.skill.md) | Single-target retrieval; `(fallback: [])` op-level fallback; LLM-with-citation pattern; pipe filter `\|json` for prompt embedding |
12
- | [`classify-support-ticket.skill.md`](./skillscripts/classify-support-ticket.skill.md) | `if`/`elif`/`else` multi-branch routing; classifier-cascade pattern; MemoryStore `$` writes with structured `domain_tags`; `$set` literal binding |
12
+ | [`classify-support-ticket.skill.md`](./skillscripts/classify-support-ticket.skill.md) | `if`/`elif`/`else` multi-branch routing; classifier-cascade pattern; DataStore `$` writes with structured `domain_tags`; `$set` literal binding |
13
13
  | [`cut-release-tag.skill.md`](./skillscripts/cut-release-tag.skill.md) | `??` interactive ask-for-input; `else:` short-circuit on multiple targets; `@` shell ops with per-op error fallback; rollback-on-failure flow |
14
- | [`service-health-watch.skill.md`](./skillscripts/service-health-watch.skill.md) | `foreach` iteration; cron-fired with `expires_at=$(EVENT.fired_at_plus_1d_unix)` TTL math; classifier-gated MemoryStore writes; pipe filter `\|url` for path-safe interpolation |
14
+ | [`service-health-watch.skill.md`](./skillscripts/service-health-watch.skill.md) | `foreach` iteration; cron-fired with `expires_at=$(EVENT.fired_at_plus_1d_unix)` TTL math; classifier-gated DataStore writes; pipe filter `\|url` for path-safe interpolation |
15
15
  | [`feedback-sentiment-scan.skill.md`](./skillscripts/feedback-sentiment-scan.skill.md) | `in` / `not in` set-membership ops; dedupe-via-seen-markers idiom; nested `if`/`elif` classification cascade; TTL on bookkeeping writes |
16
16
 
17
17
  ## Connector requirements
@@ -19,9 +19,9 @@ For full language semantics, see [`../docs/language-reference.md`](../docs/langu
19
19
  Most examples assume the bundled-default connectors:
20
20
 
21
21
  - `hello.skill.md` — zero deps (runs cold)
22
- - `doc-qa-with-citations.skill.md` — Ollama (LocalModel) + MemoryStore for the retrieval cache
23
- - `morning-brief.skill.md`, `feedback-sentiment-scan.skill.md` — Ollama + MemoryStore + a calendar MCP connector (the `$ calendar.list_events` op)
24
- - `classify-support-ticket.skill.md` — Ollama + MemoryStore
22
+ - `doc-qa-with-citations.skill.md` — Ollama (LocalModel) + DataStore for the retrieval cache
23
+ - `morning-brief.skill.md`, `feedback-sentiment-scan.skill.md` — Ollama + DataStore + a calendar MCP connector (the `$ calendar.list_events` op)
24
+ - `classify-support-ticket.skill.md` — Ollama + DataStore
25
25
  - `cut-release-tag.skill.md` — git CLI on `PATH`; expects to run inside a git repo
26
26
  - `service-health-watch.skill.md` — `curl` on `PATH`; network egress to `status.internal/*`
27
27
 
@@ -1,44 +1,44 @@
1
1
  /**
2
- * MemoryStoreTemplate — fork-me skeleton for writing your own MemoryStore impl.
2
+ * DataStoreTemplate — fork-me skeleton for writing your own DataStore impl.
3
3
  *
4
4
  * This is NOT a runnable connector. Every method throws a "TODO" error. The
5
- * purpose is to surface the MemoryStore contract surface in a copy-and-customize
5
+ * purpose is to surface the DataStore contract surface in a copy-and-customize
6
6
  * shape so adopters writing AMP-, Pinecone-, Weaviate-, Qdrant-, or
7
- * Postgres-backed MemoryStores have a starting skeleton.
7
+ * Postgres-backed DataStores have a starting skeleton.
8
8
  *
9
9
  * Forking workflow:
10
- * 1. Copy this directory into your codebase (`cp -r examples/connectors/MemoryStoreTemplate examples/connectors/MyMemoryStore`)
11
- * 2. Rename the class — typically `<Substrate>MemoryStore` (e.g., `PineconeMemoryStore`, `AmpMemoryStore`)
10
+ * 1. Copy this directory into your codebase (`cp -r examples/connectors/DataStoreTemplate examples/connectors/MyDataStore`)
11
+ * 2. Rename the class — typically `<Substrate>DataStore` (e.g., `PineconeDataStore`, `AmpDataStore`)
12
12
  * 3. Implement each method against your substrate's API
13
13
  * 4. Update `staticCapabilities()` to declare what your impl actually supports
14
14
  * 5. Register from your adopter bootstrap:
15
- * `registry.registerMemoryStore("primary", new MyMemoryStore({ ... }))`
15
+ * `registry.registerDataStore("primary", new MyDataStore({ ... }))`
16
16
  * 6. Validate via the conformance suite:
17
- * `MemoryStoreConformance.buildTests({ build: () => new MyMemoryStore(...), ctor: MyMemoryStore })`
17
+ * `DataStoreConformance.buildTests({ build: () => new MyDataStore(...), ctor: MyDataStore })`
18
18
  *
19
- * See `src/connectors/memory-store.ts` for the working reference implementation
20
- * (`SqliteMemoryStore` — SQLite + FTS5 backing). The full contract spec lives in
21
- * `src/connectors/types.ts` (`MemoryStore` interface + `PortableMemory` +
22
- * `QueryFilters` + `MemoryWrite` types).
19
+ * See `src/connectors/data-store.ts` for the working reference implementation
20
+ * (`SqliteDataStore` — SQLite + FTS5 backing). The full contract spec lives in
21
+ * `src/connectors/types.ts` (`DataStore` interface + `PortableData` +
22
+ * `QueryFilters` + `DataWrite` types).
23
23
  *
24
- * Runtime hosts (MCP server + web dashboard) honor whichever MemoryStore impl
24
+ * Runtime hosts (MCP server + web dashboard) honor whichever DataStore impl
25
25
  * you register via the registry, so once your fork passes the conformance
26
- * suite the entire `$ memory` / `$ memory_write` dispatch path reads + writes
26
+ * suite the entire `$ data_read` / `$ data_write` dispatch path reads + writes
27
27
  * against your substrate.
28
28
  */
29
29
 
30
30
  import type {
31
- MemoryStore,
31
+ DataStore,
32
32
  QueryFilters,
33
- PortableMemory,
34
- MemoryWrite,
35
- MemoryWriteRecord,
36
- MemoryStoreCapabilities,
33
+ PortableData,
34
+ DataWrite,
35
+ DataWriteRecord,
36
+ DataStoreCapabilities,
37
37
  ManifestInfo,
38
38
  } from "skillscript-runtime/connectors";
39
39
 
40
40
  /** Replace with your substrate's connection config (host, dbName, API key, etc.). */
41
- export interface MemoryStoreTemplateConfig {
41
+ export interface DataStoreTemplateConfig {
42
42
  // TODO — declare the fields your substrate needs to connect.
43
43
  // Examples:
44
44
  // pineconeApiKey?: string;
@@ -48,16 +48,16 @@ export interface MemoryStoreTemplateConfig {
48
48
  exampleConfigField?: string;
49
49
  }
50
50
 
51
- export class MemoryStoreTemplate implements MemoryStore {
51
+ export class DataStoreTemplate implements DataStore {
52
52
  /**
53
53
  * Declare what your impl supports. The runtime + lint consult these flags
54
54
  * before exercising features. Set conservatively — overclaiming triggers
55
55
  * cryptic downstream failures; underclaiming hides usable features.
56
56
  */
57
- static staticCapabilities(): MemoryStoreCapabilities {
57
+ static staticCapabilities(): DataStoreCapabilities {
58
58
  return {
59
- connector_type: "memory_store",
60
- implementation: "MemoryStoreTemplate", // ← rename to your class name
59
+ connector_type: "data_store",
60
+ implementation: "DataStoreTemplate", // ← rename to your class name
61
61
  contract_version: "1.0.0",
62
62
  features: {
63
63
  // TODO — set each flag based on what your substrate can actually do.
@@ -73,29 +73,36 @@ export class MemoryStoreTemplate implements MemoryStore {
73
73
  }
74
74
 
75
75
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
76
- constructor(_config: MemoryStoreTemplateConfig) {
76
+ constructor(_config: DataStoreTemplateConfig) {
77
77
  // TODO — establish your substrate connection. For SQL: open the database
78
78
  // + run schema migrations. For vector DB: cache the SDK client + index
79
79
  // name. For HTTP: store the base URL + auth headers.
80
- throw new Error("MemoryStoreTemplate is a fork-me skeleton; replace with your impl.");
80
+ throw new Error("DataStoreTemplate is a fork-me skeleton; replace with your impl.");
81
81
  }
82
82
 
83
83
  /**
84
84
  * Capability snapshot for `runtime_capabilities` discovery. Return free-form
85
85
  * substrate-specific metadata (kind, version, supported modes, score range, etc.).
86
86
  *
87
- * The bundled `SqliteMemoryStore.manifest()` returns:
87
+ * The bundled `SqliteDataStore.manifest()` returns:
88
88
  * { capabilities_version: "1", manifest: { kind: "sqlite-fts",
89
89
  * supported_modes: ["fts"], score_range: "unbounded",
90
90
  * supported_filters: ["domain_tags"], supports_write: true } }
91
+ *
92
+ * **v0.14.1 — `supported_filters` is load-bearing.** The DataStoreMcpConnector
93
+ * bridge enforces this declaration: filter keys passed to `$ data_read` that
94
+ * aren't in `supported_filters` throw `UnsupportedFilterError` at the bridge
95
+ * BEFORE your `query()` runs. Adopters can opt out per-call with
96
+ * `permissive_filters: true`. Declare every filter your `query()` actually
97
+ * honors here; omissions become silent "this filter is rejected".
91
98
  */
92
- async manifest(): Promise<ManifestInfo<"memory_store">> {
99
+ async manifest(): Promise<ManifestInfo<"data_store">> {
93
100
  // TODO — return a snapshot of your substrate's capabilities.
94
101
  throw new Error("TODO: manifest() — return substrate-specific capability snapshot.");
95
102
  }
96
103
 
97
104
  /**
98
- * Query memories by mode + filter. Return `PortableMemory[]` ordered by
105
+ * Query memories by mode + filter. Return `PortableData[]` ordered by
99
106
  * relevance (most relevant first). Empty result is fine; never throw "not
100
107
  * found" — return [].
101
108
  *
@@ -103,13 +110,19 @@ export class MemoryStoreTemplate implements MemoryStore {
103
110
  * - `query`: string (search terms; substrate-specific interpretation)
104
111
  * - `limit`: number (max results)
105
112
  * - `mode`: "fts" | "semantic" | "rerank" | substrate-specific string
106
- * - Plus arbitrary additional filter fields (`domain_tags`, `thread_status`,
107
- * `payload_type`, `pinned`, `agent_id`, etc.) — substrate honors what
108
- * it supports, ignores the rest. Per the curated-subset framing in
109
- * `types.ts`, these top-level fields are first-class for substrates
110
- * that have them.
113
+ * - Plus filter fields you declared in `manifest().supported_filters`
114
+ * (`domain_tags`, `thread_status`, `payload_type`, `pinned`, `agent_id`,
115
+ * etc.). Per the curated-subset framing in `types.ts`, these top-level
116
+ * fields are first-class for substrates that have them.
117
+ *
118
+ * **v0.14.1 — what reaches `query()`.** The DataStoreMcpConnector bridge
119
+ * pre-filters the keys against your `manifest().supported_filters`
120
+ * declaration; unsupported keys throw `UnsupportedFilterError` upstream and
121
+ * never reach this method. So when `query()` runs, every non-base key in
122
+ * `filters` is one your manifest declared. Don't defensively code for
123
+ * "filter the caller didn't declare" — that's the bridge's job.
111
124
  *
112
- * `PortableMemory` core fields:
125
+ * `PortableData` core fields:
113
126
  * - Always: `id`, `summary`, `created_at`
114
127
  * - Often: `detail`, `score`, `domain_tags`, `payload_type`
115
128
  * - Per-substrate (curated): `pinned`, `confidence`, `thread_status`,
@@ -117,24 +130,24 @@ export class MemoryStoreTemplate implements MemoryStore {
117
130
  * - Catch-all: `metadata` object for substrate-specific extensions
118
131
  */
119
132
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
120
- async query(_filters: QueryFilters): Promise<PortableMemory[]> {
133
+ async query(_filters: QueryFilters): Promise<PortableData[]> {
121
134
  // TODO — translate `filters` to your substrate's query API.
122
135
  // - Reject unsupported `mode` values with a clear error (don't silently
123
136
  // fall back; cold authors will be confused)
124
137
  // - Apply curated-subset filters (domain_tags, payload_type, etc.) where
125
138
  // the substrate supports them
126
- // - Map your substrate's result rows into `PortableMemory` shape
139
+ // - Map your substrate's result rows into `PortableData` shape
127
140
  // - Order by relevance (most relevant first)
128
141
  // - Honor `limit`
129
- throw new Error("TODO: query() — return PortableMemory[] ordered by relevance.");
142
+ throw new Error("TODO: query() — return PortableData[] ordered by relevance.");
130
143
  }
131
144
 
132
145
  /**
133
- * Persist a new memory entry. Return the substrate-assigned `id` +
146
+ * Persist a new data record. Return the substrate-assigned `id` +
134
147
  * `created_at` (unix seconds).
135
148
  *
136
- * `MemoryWrite` shape:
137
- * - `content`: string (required; the memory body)
149
+ * `DataWrite` shape:
150
+ * - `content`: string (required; the record body)
138
151
  * - `tags`: string[] (optional; routed to substrate's tag mechanism)
139
152
  * - `recipients`: string[] (optional advisory — substrates with alerting
140
153
  * machinery use this to route notifications, e.g., AMP's mailbox model)
@@ -143,13 +156,13 @@ export class MemoryStoreTemplate implements MemoryStore {
143
156
  * extensions like `vault`, `payload_type`, `confidence`)
144
157
  */
145
158
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
146
- async write(_entry: MemoryWrite): Promise<MemoryWriteRecord> {
147
- // TODO — persist the memory via your substrate.
159
+ async write(_entry: DataWrite): Promise<DataWriteRecord> {
160
+ // TODO — persist the record via your substrate.
148
161
  // - Generate or accept an id (substrate-dependent)
149
162
  // - Persist content + tags + metadata
150
163
  // - Apply recipients hint if your substrate has alerting
151
164
  // - Apply expires_at if your substrate has TTL
152
165
  // - Return { id, created_at }
153
- throw new Error("TODO: write() — persist memory; return { id, created_at }.");
166
+ throw new Error("TODO: write() — persist record; return { id, created_at }.");
154
167
  }
155
168
  }