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.
- package/README.md +12 -12
- package/dist/bootstrap.d.ts +13 -13
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +31 -31
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.js +6 -6
- package/dist/cli.js.map +1 -1
- package/dist/compile.js +2 -2
- package/dist/compile.js.map +1 -1
- package/dist/connectors/config.d.ts +4 -4
- package/dist/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +7 -7
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/data-store-mcp.d.ts +29 -0
- package/dist/connectors/data-store-mcp.d.ts.map +1 -0
- package/dist/connectors/{memory-store-mcp.js → data-store-mcp.js} +56 -31
- package/dist/connectors/data-store-mcp.js.map +1 -0
- package/dist/connectors/{memory-store.d.ts → data-store.d.ts} +12 -12
- package/dist/connectors/data-store.d.ts.map +1 -0
- package/dist/connectors/{memory-store.js → data-store.js} +7 -7
- package/dist/connectors/data-store.js.map +1 -0
- package/dist/connectors/filter-enforcement.d.ts +18 -0
- package/dist/connectors/filter-enforcement.d.ts.map +1 -0
- package/dist/connectors/filter-enforcement.js +57 -0
- package/dist/connectors/filter-enforcement.js.map +1 -0
- package/dist/connectors/index.d.ts +5 -5
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/local-model-mcp.js +1 -1
- package/dist/connectors/local-model-mcp.js.map +1 -1
- package/dist/connectors/registry.d.ts +10 -10
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +11 -11
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/sqlite-skill-store.js +1 -1
- package/dist/connectors/sqlite-skill-store.js.map +1 -1
- package/dist/connectors/types.d.ts +36 -23
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/types.js +2 -2
- package/dist/connectors/types.js.map +1 -1
- package/dist/dashboard/spa/app.js +2 -2
- package/dist/errors.d.ts +43 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +70 -4
- package/dist/errors.js.map +1 -1
- package/dist/help-content.js +27 -27
- package/dist/help-content.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +48 -56
- package/dist/lint.js.map +1 -1
- package/dist/mcp-server.d.ts +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +19 -16
- package/dist/mcp-server.js.map +1 -1
- package/dist/mutation-gate.d.ts +61 -0
- package/dist/mutation-gate.d.ts.map +1 -0
- package/dist/mutation-gate.js +89 -0
- package/dist/mutation-gate.js.map +1 -0
- package/dist/runtime-config.d.ts +2 -2
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +1 -1
- package/dist/runtime-config.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +59 -17
- package/dist/runtime.js.map +1 -1
- package/dist/skill-manager.js +1 -1
- package/dist/skill-manager.js.map +1 -1
- package/dist/testing/conformance.d.ts +8 -8
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +8 -8
- package/dist/testing/conformance.js.map +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/trace.d.ts +3 -3
- package/docs/adopter-playbook.md +85 -41
- package/docs/configuration.md +8 -8
- package/docs/connector-contract-reference.md +9 -9
- package/docs/language-reference.md +38 -63
- package/docs/sqlite-skill-store.md +2 -2
- package/examples/README.md +5 -5
- package/examples/connectors/{MemoryStoreTemplate/MemoryStoreTemplate.ts → DataStoreTemplate/DataStoreTemplate.ts} +56 -43
- package/examples/connectors/{MemoryStoreTemplate → DataStoreTemplate}/README.md +39 -39
- package/examples/connectors/LocalModelTemplate/README.md +1 -1
- package/examples/connectors/McpConnectorTemplate/McpConnectorTemplate.ts +2 -2
- package/examples/connectors/McpConnectorTemplate/README.md +5 -5
- package/examples/connectors/README.md +3 -3
- package/examples/connectors/SkillStoreTemplate/README.md +1 -1
- package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +1 -1
- package/examples/custom-bootstrap.example.ts +15 -8
- package/examples/onboarding-scaffold/README.md +9 -10
- package/examples/onboarding-scaffold/bootstrap.ts +18 -13
- package/examples/onboarding-scaffold/connectors.json +1 -1
- package/examples/onboarding-scaffold/{file-memory-store.ts → file-data-store.ts} +34 -23
- package/examples/programmatic-trace-demo.mjs +1 -1
- package/examples/skillscripts/classify-support-ticket.skill.md +9 -8
- package/examples/skillscripts/cut-release-tag.skill.md +6 -6
- package/examples/skillscripts/doc-qa-with-citations.skill.md +3 -3
- package/examples/skillscripts/feedback-sentiment-scan.skill.md +9 -8
- package/examples/skillscripts/hello.skill.provenance.json +1 -1
- package/examples/skillscripts/morning-brief.skill.md +3 -3
- package/examples/skillscripts/service-health-watch.skill.md +5 -4
- package/package.json +1 -1
- package/scaffold/config.toml +6 -4
- package/scaffold/connectors.json +3 -3
- package/dist/connectors/memory-store-mcp.d.ts +0 -20
- package/dist/connectors/memory-store-mcp.d.ts.map +0 -1
- package/dist/connectors/memory-store-mcp.js.map +0 -1
- package/dist/connectors/memory-store.d.ts.map +0 -1
- package/dist/connectors/memory-store.js.map +0 -1
- /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 /
|
|
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
|
-
| **
|
|
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
|
|
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`, `$
|
|
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
|
-
- `$
|
|
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
|
-
| `
|
|
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 `$
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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
|
-
$
|
|
497
|
-
$
|
|
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`, `$
|
|
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
|
-
- `$
|
|
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
|
-
$
|
|
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**.
|
|
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
|
-
$
|
|
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
|
|
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.
|
|
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
|
-
- `${
|
|
645
|
+
- `${VAR.field}` / `${VAR.nested.field}` — dotted field access into structured output
|
|
669
646
|
|
|
670
|
-
|
|
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
|
-
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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
|
|
887
|
+
$ llm prompt="List the URGENT item IDs as a JSON array of strings. Items: ${M|json}" -> SEEN
|
|
913
888
|
|
|
914
|
-
foreach M in ${
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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
|
-
$
|
|
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 (
|
|
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
|
|
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=[
|
|
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-
|
|
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
|
-
|
|
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,
|
|
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`)
|
package/examples/README.md
CHANGED
|
@@ -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;
|
|
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
|
|
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) +
|
|
23
|
-
- `morning-brief.skill.md`, `feedback-sentiment-scan.skill.md` — Ollama +
|
|
24
|
-
- `classify-support-ticket.skill.md` — Ollama +
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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/
|
|
11
|
-
* 2. Rename the class — typically `<Substrate>
|
|
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.
|
|
15
|
+
* `registry.registerDataStore("primary", new MyDataStore({ ... }))`
|
|
16
16
|
* 6. Validate via the conformance suite:
|
|
17
|
-
* `
|
|
17
|
+
* `DataStoreConformance.buildTests({ build: () => new MyDataStore(...), ctor: MyDataStore })`
|
|
18
18
|
*
|
|
19
|
-
* See `src/connectors/
|
|
20
|
-
* (`
|
|
21
|
-
* `src/connectors/types.ts` (`
|
|
22
|
-
* `QueryFilters` + `
|
|
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
|
|
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 `$
|
|
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
|
-
|
|
31
|
+
DataStore,
|
|
32
32
|
QueryFilters,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
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():
|
|
57
|
+
static staticCapabilities(): DataStoreCapabilities {
|
|
58
58
|
return {
|
|
59
|
-
connector_type: "
|
|
60
|
-
implementation: "
|
|
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:
|
|
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("
|
|
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 `
|
|
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<"
|
|
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 `
|
|
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
|
|
107
|
-
* `payload_type`, `pinned`, `agent_id`,
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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
|
-
* `
|
|
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<
|
|
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 `
|
|
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
|
|
142
|
+
throw new Error("TODO: query() — return PortableData[] ordered by relevance.");
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
/**
|
|
133
|
-
* Persist a new
|
|
146
|
+
* Persist a new data record. Return the substrate-assigned `id` +
|
|
134
147
|
* `created_at` (unix seconds).
|
|
135
148
|
*
|
|
136
|
-
* `
|
|
137
|
-
* - `content`: string (required; the
|
|
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:
|
|
147
|
-
// TODO — persist the
|
|
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
|
|
166
|
+
throw new Error("TODO: write() — persist record; return { id, created_at }.");
|
|
154
167
|
}
|
|
155
168
|
}
|