skillscript-runtime 0.27.2 → 0.27.3
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/CHANGELOG.md +10 -0
- package/dist/dashboard/spa/app.js +8 -2
- package/docs/adopter-playbook.md +9 -7
- package/docs/connector-contract-reference.md +28 -26
- package/docs/language-reference.md +3 -3
- package/examples/skillscripts/classify-support-ticket.skill.provenance.json +1 -1
- package/examples/skillscripts/data-store-roundtrip.skill.provenance.json +1 -1
- package/examples/skillscripts/doc-qa-with-citations.skill.provenance.json +1 -1
- package/examples/skillscripts/feedback-sentiment-scan.skill.provenance.json +1 -1
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/examples/skillscripts/morning-brief.skill.provenance.json +1 -1
- package/examples/skillscripts/queue-length-monitor.skill.provenance.json +1 -1
- package/examples/skillscripts/service-health-watch.skill.provenance.json +1 -1
- package/examples/skillscripts/skill-store-roundtrip.skill.provenance.json +1 -1
- package/examples/skillscripts/youtrack-morning-sweep.skill.provenance.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Each release carries an **Upgrade impact:** line (first in its section) so a bump's requirements are visible at a glance. Tags (closed set): **BREAKING** (a manual change is needed to keep working) · **RE-APPROVE** (secured-mode signature invalidation — skills must be re-approved before they run) · **CONFIG** (`connectors.json` / config edit needed) · **none (additive)** (no action; backward-compatible). Standard from 0.20.0 forward; the pre-0.20 transitions that need action are flagged inline below (0.14.0, 0.18.8, 0.19.0). Full walkthrough: [UPGRADING.md](UPGRADING.md).
|
|
4
4
|
|
|
5
|
+
## 0.27.3 — 2026-07-10 — dashboard highlighter fix + adopter-facing docs audit
|
|
6
|
+
|
|
7
|
+
**Upgrade impact:** none (additive). One dashboard rendering fix + docs; no runtime behavior change.
|
|
8
|
+
|
|
9
|
+
- **Dashboard: fixed the skill-source security highlighter leaking character offsets into the rendered body.** The approval-review source viewer was showing stray numbers glued to mutation ops and `approved=` kwargs (e.g. `$ data_write2861 … approved="…"3021`). The highlight pass's single-capture-group patterns were treating `String.replace`'s third callback argument — the match *offset*, a number — as a capture group, and rendering it. Fixed (`typeof p2 === "string"` guard); added a regression test that fails on the leak. The stored skill source was never affected — this was render-only.
|
|
10
|
+
- **Adopter-facing docs audit** (`adopter-agent-guide.md`, `adopter-playbook.md`, `connector-contract-reference.md`). Pre-announcement pass for accuracy + no internal leakage:
|
|
11
|
+
- Removed internal-process references (thread ids, internal-agent names, dev-cadence/roadmap phrasing, an internal substrate name used as an example) and stripped version-provenance citations, keeping only the genuine `v1.0` *contract-version* mentions.
|
|
12
|
+
- **Accuracy:** the `SkillStore` contract block + ship-status table were missing `manifest()` (claimed 8 methods, listed 7) — corrected, and `DataStore` made consistent; the `AgentConnector` `WakeReceipt` interface block was missing `warnings?: string[]` — added; the agent-guide MCP tool reference completed from 13 to the full **17** tools.
|
|
13
|
+
- **`language-reference.md` re-rendered from atoms** — the `# OnError:` deprecation section now states removal is planned for a post-launch release (after 0.30), replacing the earlier reference to a version that will be skipped.
|
|
14
|
+
|
|
5
15
|
## 0.27.2 — 2026-07-09 — launch doc-honesty + onboarding caption
|
|
6
16
|
|
|
7
17
|
**Upgrade impact:** none (additive). Docs, help-content, one error message, and CLI onboarding copy; no runtime behavior change.
|
|
@@ -693,8 +693,14 @@ function renderHighlightedSkillBody(source) {
|
|
|
693
693
|
];
|
|
694
694
|
for (const re of highPatterns) {
|
|
695
695
|
body = body.replace(re, (_match, p1, p2) => {
|
|
696
|
-
|
|
697
|
-
|
|
696
|
+
// Only the `# Autonomous:` pattern has a second CAPTURE group (the
|
|
697
|
+
// leading newline/BOL is p1, the signal is p2). The single-group
|
|
698
|
+
// patterns receive the match OFFSET as the third arg — a NUMBER, not a
|
|
699
|
+
// capture — so test for a string, not `!== undefined`, or the offset
|
|
700
|
+
// gets rendered as highlighted text (e.g. `$ data_write2861`).
|
|
701
|
+
const hasPrefixGroup = typeof p2 === "string";
|
|
702
|
+
const prefix = hasPrefixGroup ? p1 : "";
|
|
703
|
+
const captured = hasPrefixGroup ? p2 : p1;
|
|
698
704
|
return `${prefix}<span class="sig-high">${captured}</span>`;
|
|
699
705
|
});
|
|
700
706
|
}
|
package/docs/adopter-playbook.md
CHANGED
|
@@ -28,6 +28,7 @@ DataStore — substrate-neutral data persistence
|
|
|
28
28
|
query(filters) runtime asks: "find records matching these filters"
|
|
29
29
|
write(record) runtime asks: "store this; return id + timestamp"
|
|
30
30
|
get(id) runtime asks: "give me this specific record"
|
|
31
|
+
manifest() runtime asks: "what can you do?" (capability snapshot)
|
|
31
32
|
|
|
32
33
|
SkillStore — substrate-neutral skill source persistence
|
|
33
34
|
load(name) "give me this skill's source"
|
|
@@ -37,6 +38,7 @@ SkillStore — substrate-neutral skill source persistence
|
|
|
37
38
|
versions(name) "audit trail"
|
|
38
39
|
metadata(name) "header info without body"
|
|
39
40
|
delete(name) "remove all versions"
|
|
41
|
+
manifest() "substrate capability snapshot"
|
|
40
42
|
|
|
41
43
|
LocalModel — substrate-neutral LLM dispatch
|
|
42
44
|
run(prompt, opts) "complete this prompt; return text"
|
|
@@ -368,7 +370,7 @@ Every skill carries a lifecycle status — `Draft → Approved → Disabled`. **
|
|
|
368
370
|
|
|
369
371
|
Approval is an Ed25519 signature, not a shared secret:
|
|
370
372
|
|
|
371
|
-
- **Private key** — held by the operator, read **only** by the approve flow. That flow is either `skillfile approve` at a terminal (the runtime process never loads the key) or, if you opt into in-browser approval, the dashboard's passcode-gated `/approve` — which reads the key only after a live human enters the session passcode, never on its own. For maximum isolation, keep signing terminal-side and run the runtime under a uid that cannot read the private key — then a co-resident agent can't forge approvals even with full read access to the runtime process. Same-uid (or in-browser signing) still works, but isn't fully isolated
|
|
373
|
+
- **Private key** — held by the operator, read **only** by the approve flow. That flow is either `skillfile approve` at a terminal (the runtime process never loads the key) or, if you opt into in-browser approval, the dashboard's passcode-gated `/approve` — which reads the key only after a live human enters the session passcode, never on its own. For maximum isolation, keep signing terminal-side and run the runtime under a uid that cannot read the private key — then a co-resident agent can't forge approvals even with full read access to the runtime process. Same-uid (or in-browser signing) still works, but isn't fully isolated — a future managed-custody option will close this.
|
|
372
374
|
- **Public key** — non-secret; the runtime reads it to *verify* signatures on every execution. No hot-path secret is the point: verification needs only the public key.
|
|
373
375
|
|
|
374
376
|
First-run provisioning is automatic — starting the runtime or `skillfile init` in secured mode generates the keypair if absent (private written `0600`, default `~/.config/skillscript/approval.{key,pub}`, deliberately outside `SKILLSCRIPT_HOME`, the agent-readable data dir).
|
|
@@ -514,7 +516,7 @@ To wire your **own custom substrate** (e.g. a remote `SkillStore`), two paths:
|
|
|
514
516
|
|
|
515
517
|
This closes the silent CLI-vs-programmatic asymmetry described below; reach for raw `bootstrap()` only when you're hand-assembling a registry that neither `connectors.json` nor `overrides` can express.
|
|
516
518
|
|
|
517
|
-
**⚠ Migrating an existing hand-assembled bootstrap to `bootstrapFromEnv()`?** Move any options you previously hardcoded on `bootstrap()` / `new DashboardServer({...})` to their `SKILLSCRIPT_*` env equivalents — `bootstrapFromEnv()` resolves them from env, so a value you drop reverts to the default. Two to watch
|
|
519
|
+
**⚠ Migrating an existing hand-assembled bootstrap to `bootstrapFromEnv()`?** Move any options you previously hardcoded on `bootstrap()` / `new DashboardServer({...})` to their `SKILLSCRIPT_*` env equivalents — `bootstrapFromEnv()` resolves them from env, so a value you drop reverts to the default. Two to watch: `enableUnsafeShell: true` → `SKILLSCRIPT_ENABLE_UNSAFE_SHELL=true` (fails **loud** — unsafe ops just refuse, you'll notice); and **`mcpCallerIdentityHeader: "X-Agent-Id"` → `SKILLSCRIPT_MCP_CALLER_IDENTITY_HEADER=X-Agent-Id`, which fails *silently*** — drop it and skill-author attribution quietly reverts to the store's default writer identity, with no error. (`bootstrap()`-level opts like `enableUnsafeShell` can instead go through `overrides`; the `DashboardServer`-level `mcpCallerIdentityHeader` is env-only via `bootstrapFromEnv` — set the env var.) Verify after migrating: send your identity header on a `/rpc` `skill_write` and confirm the captured `author`.
|
|
518
520
|
|
|
519
521
|
#### Raw `bootstrap()` — the CLI-auto-vs-programmatic-explicit asymmetry
|
|
520
522
|
|
|
@@ -608,7 +610,7 @@ Per-skill capability declaration: skills declare what shell binaries they need i
|
|
|
608
610
|
# Status: Approved
|
|
609
611
|
```
|
|
610
612
|
|
|
611
|
-
The operator policy validates `declared ∩ allowlist` — each skill's shell footprint becomes self-documenting and auditable.
|
|
613
|
+
The operator policy validates `declared ∩ allowlist` — each skill's shell footprint becomes self-documenting and auditable. Planned for a future release.
|
|
612
614
|
|
|
613
615
|
## Filesystem path allowlist
|
|
614
616
|
|
|
@@ -1185,8 +1187,8 @@ See `examples/custom-bootstrap.example.ts` for a worked walkthrough.
|
|
|
1185
1187
|
|
|
1186
1188
|
| Substrate | Shipped contract | Shipped impls | Shipped bridge |
|
|
1187
1189
|
|---|---|---|---|
|
|
1188
|
-
| SkillStore | ✓ 8 methods (`load` / `query` / `store` / `update_status` / `delete` / `versions` / `metadata` / `
|
|
1189
|
-
| DataStore | ✓
|
|
1190
|
+
| SkillStore | ✓ 8 methods (`load` / `query` / `store` / `update_status` / `delete` / `versions` / `metadata` / `manifest`) + `staticCapabilities` | `FilesystemSkillStore`, `SqliteSkillStore` | n/a |
|
|
1191
|
+
| DataStore | ✓ 4 methods (`query` / `write` / `get` / `manifest`) + `staticCapabilities` | `SqliteDataStore` | ✓ `DataStoreMcpConnector` |
|
|
1190
1192
|
| LocalModel | ✓ 1 method (`run`) | `OllamaLocalModel` | ✓ `LocalModelMcpConnector` |
|
|
1191
1193
|
| McpConnector | ✓ 1 method (`call`) | `RemoteMcpConnector`, `CallbackMcpConnector` | n/a |
|
|
1192
1194
|
| AgentConnector | ✓ 5 required (`list_agents` / `deliver` / `wake` / `health_check` / `request_response`) + 1 optional (`agent_status`) | `NoOpAgentConnector` (default), `HttpWebhookAgentConnector` | n/a |
|
|
@@ -1201,7 +1203,7 @@ See `examples/custom-bootstrap.example.ts` for a worked walkthrough.
|
|
|
1201
1203
|
- **Your `DataStore.write()` is never called if the mutation gate rejects the skill.** The runtime gates `$ data_write` (and other mutation ops) upstream of the bridge — substrates only see authorized writes. If your own probes hit `UnconfirmedMutationError`, that's a skill-body issue (missing `# Autonomous: true` / `approved=`), not a substrate issue.
|
|
1202
1204
|
- **Filter scope is enforced at the bridge.** `DataStoreMcpConnector` rejects every filter key outside the substrate's declared `manifest().supported_filters` set, throwing `UnsupportedFilterError`. This prevents silent scope leaks where unsupported filters get dropped without the caller knowing. Per-call opt-out: `permissive_filters: true` acknowledges "unknown keys are advisory; substrate may ignore them." Substrate implementors: declare every filter your `query()` actually honors so the bridge validates against your truth, not a guess.
|
|
1203
1205
|
- **FTS matching strictness varies by substrate.** The `DataStore.query()` contract names the modes (`fts` / `semantic` / `rerank`) but doesn't pin down matching semantics within each mode — token-OR, phrase-tokens, fuzzy, exact, FTS5-syntax-passthrough, etc. are all conformant. The bundled `data-store-roundtrip` demo asserts `N ≥ 1` (a successful round-trip) rather than a specific count, which works across any FTS-supporting substrate. For adopters who need deterministic exact-count reads (round-trip tests, idempotency checks, exact-record-matched fetches), the portable strict-match path is `domain_tags=[...]` filtering — the bridge enforces tag-key against `supported_filters` and substrates declaring `supports_tag_filter: true` honor exact-tag any-of-match per the contract. Use FTS for relevance ranking against open content; use tag filters when you need to be sure you got the specific record you wrote.
|
|
1204
|
-
- **Durable-forever opt-in via `expires_at: null`.** `DataWrite.expires_at` accepts a unix timestamp for finite expiry, `null` to opt into "durable forever" (the portable verb for substrates with default TTL —
|
|
1206
|
+
- **Durable-forever opt-in via `expires_at: null`.** `DataWrite.expires_at` accepts a unix timestamp for finite expiry, `null` to opt into "durable forever" (the portable verb for substrates with default TTL — Redis with default expiry, hosted memory APIs), or omitted (substrate's default lifecycle, may be durable or may have decay). Substrates that are durable-by-default (the bundled `SqliteDataStore`) treat `null` as a no-op. Substrates with default sweep should map `null` to their pin / no-decay flag.
|
|
1205
1207
|
- **Two trigger primitives, both functional.** `cron` (time-based) and `event` (HTTP `/event` ingress, named registration). All other concepts that look like triggers — session-start, agent-event, file-watch, sensor — are adapter responsibilities: the adapter POSTs `/event` when relevant. Keeps the runtime substrate-neutral; the trigger surface stays tight.
|
|
1206
1208
|
- **Output kinds are intentionally substrate-neutral.** `# Output:` accepts `text` / `agent: <name>` / `template: <name>` / `file: <path>` / `none`. Substrate-specific values (`slack:`, `card:`, etc.) are out of scope — adopters wanting Slack / WhatsApp / Discord / etc. delivery use either `$ slack.post ...` MCP dispatch inside the skill body OR deliver via `agent: <name>` and let the receiving agent decide.
|
|
1207
1209
|
- **Authorization is signature-based in secured mode.** An `Approved` skill runs unkeyed in unsecured mode (a bare `# Status: Approved` is sufficient) and requires a valid Ed25519 operator signature in secured mode — verified on every execution against the operator's public key, with the private key held off the runtime. See [Approval + secured mode](#approval--secured-mode) for the model, the approve flow, key custody, and migration. The shell + filesystem allowlists bound blast radius in *both* modes; secured mode is what gates whether an unapproved skill runs at all.
|
|
@@ -1228,7 +1230,7 @@ The multi-layer-promise pattern (lint passes; runtime fails, or vice versa) is t
|
|
|
1228
1230
|
2. **Runtime test** — same shape executed end-to-end (`executeSkillByName` or `executeSkillFromSource`)
|
|
1229
1231
|
3. **E2E test** — the full user path (write skill → store → execute via MCP, or trigger fire → dispatch)
|
|
1230
1232
|
|
|
1231
|
-
PR description must call out which dispatch shape is exercised. If you can't write all three for a shape, that's a signal the shape is incompletely specified —
|
|
1233
|
+
PR description must call out which dispatch shape is exercised. If you can't write all three for a shape, that's a signal the shape is incompletely specified — open an issue before merging.
|
|
1232
1234
|
|
|
1233
1235
|
Connector class authors implementing new `McpConnectorClass`-shaped contracts should also implement `staticTools(): string[] | null` whenever the tool surface is closed and knowable at compile time. Lift `unknown-tool-on-connector` from "advisory you fix at runtime" to "tier-1 error caught at compile time" for every adopter who wires your class.
|
|
1234
1236
|
|
|
@@ -4,11 +4,11 @@ description: "The substrate-neutral contracts adopters implement to wire their o
|
|
|
4
4
|
mode: wide
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
The substrate-neutral contracts skillscript-runtime exposes for adopters to wire their own substrate behind. This doc is the **canonical source of truth** for the AgentConnector contract
|
|
7
|
+
The substrate-neutral contracts skillscript-runtime exposes for adopters to wire their own substrate behind. This doc is the **canonical source of truth** for the AgentConnector contract, whose interface shape is locked at v1.0. The wake/deliver receipt shapes carry refinements for session-targeting and graceful degradation.
|
|
8
8
|
|
|
9
9
|
**Audience**: this doc is written for the agent that's implementing an adopter's AgentConnector — typically an LLM-class agent supervised by a human. If you're a human reading it directly, the same content applies; the prose is tightened for agent comprehension (literal field semantics, explicit precedence rules, worked examples).
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The other contracts (McpConnector, SkillStore, DataStore, LocalModel) are covered in the sections below; this doc grows to cover each.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -73,7 +73,7 @@ interface DeliveryMeta {
|
|
|
73
73
|
|
|
74
74
|
- **`meta.origin.trigger_kind`**: how the originating skill was fired. Receiver routes on this without parsing content (cron-fired triage vs agent-initiated request vs webhook from external system).
|
|
75
75
|
|
|
76
|
-
- **`meta.origin.caller_agent_id`**: the AUTHENTICATED CALLER who fired the dispatch — distinct from the skill's author/owner. When an MCP `/rpc` call carries the configured caller-identity header (e.g., `X-Agent-Id:
|
|
76
|
+
- **`meta.origin.caller_agent_id`**: the AUTHENTICATED CALLER who fired the dispatch — distinct from the skill's author/owner. When an MCP `/rpc` call carries the configured caller-identity header (e.g., `X-Agent-Id: web-ui`), that value flows here. The chain originator is preserved across composition: if `web-ui` invokes Alice's skill A which composes Bob's skill B, B's notify() still emits `caller_agent_id: web-ui`. Cron / event / cli / dashboard triggers leave it undefined (no human caller); direct `execute_skill` without an identity header also leaves it undefined (the owner is NOT used as fallback — caller-identity and ownership are deliberately distinct). See [Adopter Playbook](adopter-playbook.md) §"Identity propagation" for the inbound-header wiring.
|
|
77
77
|
|
|
78
78
|
- **`meta.event_type`**: adopter-defined routing vocabulary — opaque to skillscript. Set via `notify(event_type=...)` kwarg (per-emit) OR `# Event-type:` skill frontmatter (skill-wide fallback). Kwarg takes precedence per-emit.
|
|
79
79
|
|
|
@@ -95,7 +95,7 @@ interface DeliveryReceipt {
|
|
|
95
95
|
- **`delivery_id`**: substrate-specific id for callers to correlate later.
|
|
96
96
|
- **`session_id`**: the session that received the delivery. Set when the substrate routes to a specific session (e.g., per-terminal mailbox, per-tab webhook). Omitted when the substrate is agent-level only (Slack DM, email — no session concept) or when the substrate fans out / accepts without committing to a session. See *agent@session targeting* below.
|
|
97
97
|
- **`delivery_skipped`**: adopter signals "accepted but not pushed to the agent" — offline, rate-limit drop, tmux session exists but agent hasn't read, etc. Distinct from outright failure (which throws). Runtime echoes this on the receipt record for dashboard observability.
|
|
98
|
-
- **`warnings
|
|
98
|
+
- **`warnings`**: non-fatal substrate notes about the delivery. Surfaced onto `AgentDeliveryReceiptRecord` so the dashboard + observability surfaces show them instead of substrate-side stderr noise. Examples: `"stripped @session suffix — deliver is mailbox-class"`, `"rate-limit hint: backoff 5s before next deliver"`, `"fan-out: delivered to 3 active sessions"`. Distinct from `delivery_skipped` (accepted-not-pushed) and from thrown errors (delivery failed) — warnings are advisory; the delivery succeeded, the substrate just has commentary.
|
|
99
99
|
|
|
100
100
|
### WakeOpts + WakeReceipt
|
|
101
101
|
|
|
@@ -110,6 +110,7 @@ interface WakeReceipt {
|
|
|
110
110
|
woken_at: number;
|
|
111
111
|
woken: boolean;
|
|
112
112
|
session_id?: string;
|
|
113
|
+
warnings?: string[];
|
|
113
114
|
}
|
|
114
115
|
```
|
|
115
116
|
|
|
@@ -119,41 +120,42 @@ interface WakeReceipt {
|
|
|
119
120
|
- **`WakeReceipt.woken_at`**: substrate's acknowledgement timestamp.
|
|
120
121
|
- **`WakeReceipt.woken`** (required): honest signal of whether the substrate actually interrupted the agent. See *Graceful degradation on wake* below — this is the read every caller does to distinguish interrupted-them from delivered-only.
|
|
121
122
|
- **`WakeReceipt.session_id`**: the session that received the wake (or delivery, if degraded). Set when the substrate knows; omit otherwise.
|
|
123
|
+
- **`WakeReceipt.warnings`**: optional non-fatal substrate notes about the wake — advisory commentary surfaced onto the receipt record for dashboard + observability instead of substrate-side stderr noise. Symmetric with `DeliveryReceipt.warnings`.
|
|
122
124
|
|
|
123
125
|
### agent@session targeting
|
|
124
126
|
|
|
125
127
|
`agent_id` is an opaque string. The substrate may treat it as:
|
|
126
128
|
|
|
127
129
|
- A bare agent identifier (`alice`, an email address, a Slack `@user`, a Discord user ID).
|
|
128
|
-
- A composite `agent@session` (e.g., `"
|
|
130
|
+
- A composite `agent@session` (e.g., `"alice@terminal-1"`) when the substrate tracks multiple live sessions per identity.
|
|
129
131
|
|
|
130
132
|
The substrate decomposes the composite if it cares; non-session substrates ignore the suffix or treat the whole string as the address. This keeps the contract substrate-neutral while preserving session-granular routing — every messaging substrate either addresses a bare identity OR a specific live session, and the opaque-composite form covers both without locking adopters into a particular session model.
|
|
131
133
|
|
|
132
|
-
**Address-routed dispatch
|
|
134
|
+
**Address-routed dispatch**: the runtime uses the presence of `@` in `agent_id` to decide between `deliver()` and `wake()` for skill-author surfaces (`notify()` op + `# Output: agent:` / `# Output: template:` lifecycle hooks):
|
|
133
135
|
|
|
134
136
|
| Skill-author syntax | Address shape | Connector method called |
|
|
135
137
|
|---|---|---|
|
|
136
|
-
| `notify(agent="
|
|
137
|
-
| `notify(agent="
|
|
138
|
-
| `# Output: agent:
|
|
139
|
-
| `# Output: agent:
|
|
140
|
-
| `# Output: template:
|
|
138
|
+
| `notify(agent="alice", …)` | bare | `deliver()` |
|
|
139
|
+
| `notify(agent="alice@terminal-1", …)` | composite | `wake()` |
|
|
140
|
+
| `# Output: agent: alice` | bare | `deliver()` |
|
|
141
|
+
| `# Output: agent: alice@terminal-1` | composite | `wake()` |
|
|
142
|
+
| `# Output: template: alice@tab-3` | composite | `wake()` |
|
|
141
143
|
|
|
142
|
-
The runtime threads the FULL composite to `wake()` — substrate decomposes per the rule above. For wake-routed dispatches, the skill's content (notify message or accumulated emissions) rides as `WakeOpts.context`.
|
|
144
|
+
The runtime threads the FULL composite to `wake()` — substrate decomposes per the rule above. For wake-routed dispatches, the skill's content (notify message or accumulated emissions) rides as `WakeOpts.context`. The rule: "the address encodes delivery class" — same convention as the broader `waiting_on` / mailbox / broker convention. No `wake=true` kwarg exists; the `@` IS the signal.
|
|
143
145
|
|
|
144
146
|
**Two forms, one wire**:
|
|
145
147
|
|
|
146
148
|
```typescript
|
|
147
149
|
// Form A — composite in agent_id (works for deliver + wake)
|
|
148
|
-
await conn.wake("
|
|
150
|
+
await conn.wake("alice@terminal-1");
|
|
149
151
|
|
|
150
152
|
// Form B — structured WakeOpts.session_id (wake only)
|
|
151
|
-
await conn.wake("
|
|
153
|
+
await conn.wake("alice", { session_id: "terminal-1" });
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
Substrates that care about sessions read both — `opts.session_id` wins if both are set. Substrates that don't care ignore both. Callers that already have agent + session as separate variables prefer Form B; callers passing an opaque user-supplied address prefer Form A.
|
|
155
157
|
|
|
156
|
-
`DeliveryReceipt.session_id` and `WakeReceipt.session_id` echo the resolved session back to the caller. Useful for dashboards rendering "delivered to
|
|
158
|
+
`DeliveryReceipt.session_id` and `WakeReceipt.session_id` echo the resolved session back to the caller. Useful for dashboards rendering "delivered to alice@terminal-1" rather than just "delivered to alice."
|
|
157
159
|
|
|
158
160
|
### Graceful degradation on wake
|
|
159
161
|
|
|
@@ -185,7 +187,7 @@ The distinction `wake-capability` vs `network-fault` matters. The former is stru
|
|
|
185
187
|
| `notify(agent=X@session, message=..., ...)` op | composite | `AgentConnector.wake()` | n/a (message as `WakeOpts.context`) | n/a |
|
|
186
188
|
| `exchange(agent=X, message=..., timeout=...)` op (locked-shape, runtime support pending) | bare | `AgentConnector.request_response()` | `augment` | Same as notify; correlation_id required |
|
|
187
189
|
|
|
188
|
-
The address-routing rule
|
|
190
|
+
The address-routing rule is uniform across all skill-author surfaces: `@session` present → wake-class; bare → deliver-class. See *agent@session targeting* below for the contract-level convention.
|
|
189
191
|
|
|
190
192
|
---
|
|
191
193
|
|
|
@@ -208,7 +210,7 @@ Wiring failures surface at boot (health_check throws), not at first skill-fire.
|
|
|
208
210
|
|
|
209
211
|
### Writing your own AgentConnector
|
|
210
212
|
|
|
211
|
-
If you're an agent implementing this contract against an adopter substrate, the canonical worked example is `HttpWebhookAgentConnector
|
|
213
|
+
If you're an agent implementing this contract against an adopter substrate, the canonical worked example is `HttpWebhookAgentConnector`, bundled under `examples/connectors/`.
|
|
212
214
|
|
|
213
215
|
Implementation checklist:
|
|
214
216
|
|
|
@@ -234,7 +236,7 @@ If your substrate matches the shape of a bundled connector closely (e.g., HTTP w
|
|
|
234
236
|
|
|
235
237
|
---
|
|
236
238
|
|
|
237
|
-
##
|
|
239
|
+
## Load-bearing semantic footnotes
|
|
238
240
|
|
|
239
241
|
These are the load-bearing semantic rules. Internalize before implementing.
|
|
240
242
|
|
|
@@ -250,15 +252,15 @@ These are the load-bearing semantic rules. Internalize before implementing.
|
|
|
250
252
|
|
|
251
253
|
## Storage-layer conventions (SkillStore + DataStore)
|
|
252
254
|
|
|
253
|
-
The
|
|
255
|
+
The following conventions live in the bundled reference impls but aren't first-class in the typed contracts. Adopters writing their own SkillStore/DataStore impls need to know about these, or skills/memories misbehave silently.
|
|
254
256
|
|
|
255
257
|
### SkillStore conventions
|
|
256
258
|
|
|
257
|
-
**`author` field on SkillMeta + filter on `query()
|
|
259
|
+
**`author` field on SkillMeta + filter on `query()`.** `SkillMeta.author` is optional; substrates that track authorship populate it (bundled `FilesystemSkillStore` reads from `os.userInfo().username`; `SqliteSkillStore` stores at write time). Substrates without an authorship concept leave it `undefined`; the catalog layer surfaces `null` to the wire.
|
|
258
260
|
|
|
259
|
-
`SkillStore.query({ author: "X" })` is an optional substrate-honored filter. Substrates that natively track authorship can filter at the substrate layer; substrates that don't return all status-matching rows and the `buildSkillCatalog()` layer filters in-memory per `meta.author`. Either way the caller sees only matching authors
|
|
261
|
+
`SkillStore.query({ author: "X" })` is an optional substrate-honored filter. Substrates that natively track authorship can filter at the substrate layer; substrates that don't return all status-matching rows and the `buildSkillCatalog()` layer filters in-memory per `meta.author`. Either way the caller sees only matching authors — a generic, connector-implemented, graceful-degrading filter. The substrate-neutrality property holds — adopters wire whichever shape fits their ownership model.
|
|
260
262
|
|
|
261
|
-
Adopter substrates with their own ownership concept (e.g.,
|
|
263
|
+
Adopter substrates with their own ownership concept (e.g., a native `author:<id>` tag) should map the filter onto their native query so subset-fetching stays efficient. Adopters with no ownership concept can leave `query()` unchanged and let the catalog-layer in-memory filter handle narrowing.
|
|
262
264
|
|
|
263
265
|
|
|
264
266
|
|
|
@@ -276,9 +278,9 @@ Adopter substrates with their own ownership concept (e.g., AMP's `author:<id>` t
|
|
|
276
278
|
|
|
277
279
|
### DataStore conventions
|
|
278
280
|
|
|
279
|
-
**`summary`/`detail` split is convention, not contract field.** The DataStore contract gives `write()` a single `content: string`. Bundled `SqliteDataStore` maps this to `summary = first line (≤200 chars)` and `detail = full content`. Adopter substrates with native summary/detail concepts (
|
|
281
|
+
**`summary`/`detail` split is convention, not contract field.** The DataStore contract gives `write()` a single `content: string`. Bundled `SqliteDataStore` maps this to `summary = first line (≤200 chars)` and `detail = full content`. Adopter substrates with native summary/detail concepts (their own `summary` + `detail` columns) can pre-compose and pass via `metadata`, but the basic mapping convention is "first line is the preview." Diverge and the dashboard's memory rendering looks weird, but skills still work.
|
|
280
282
|
|
|
281
|
-
**`get(id)` returns null on miss, doesn't throw.** Distinct from SkillStore's `load(name)` which throws `SkillNotFoundError`. DataStore's empty-set convention (`query()` returns `[]` not throws; `get()` returns `null` not throws) is **load-bearing for the runtime's control flow** — query callers branch on `result.length`, get callers branch on `result === null`. Don't change this in your impl.
|
|
283
|
+
**`get(id)` returns null on miss, doesn't throw.** Distinct from SkillStore's `load(name)` which throws `SkillNotFoundError`. DataStore's empty-set convention (`query()` returns `[]` not throws; `get()` returns `null` not throws) is **load-bearing for the runtime's control flow** — query callers branch on `result.length`, get callers branch on `result === null`. Don't change this in your impl.
|
|
282
284
|
|
|
283
285
|
### Durability stance (both contracts)
|
|
284
286
|
|
|
@@ -298,8 +300,8 @@ Implementer responsibility: **declare every filter your `query()` actually honor
|
|
|
298
300
|
|
|
299
301
|
### Why these aren't in the typed interface
|
|
300
302
|
|
|
301
|
-
The shape-vs-semantics split is deliberate
|
|
303
|
+
The shape-vs-semantics split is deliberate: the typed contract guarantees shape portability (same methods, same return types); the conventions above are semantic portability concerns that the contract chose not to encode. Bundled impls follow them; custom impls SHOULD follow them. Capability flags + manifest fields make some conventions inspectable at runtime (`regexp_fallback_active`, `supported_filters`, `supported_modes`), but most live in source comments + this doc.
|
|
302
304
|
|
|
303
305
|
---
|
|
304
306
|
|
|
305
|
-
*
|
|
307
|
+
*Future contract changes update this file alongside the code; the CHANGELOG owns version history.*
|
|
@@ -1861,7 +1861,7 @@ Test, Deployed, and Deprecated were considered and deferred — today's Draft/Ap
|
|
|
1861
1861
|
|
|
1862
1862
|
Lifecycle states are the language's operational-safety answer. A Disabled skill can't fire even if every author forgets it's broken; in secured mode, an Approved skill can't fire if its body was tampered post-signature, and an agent can't approve its own work. The constraint IS the safety story, here as elsewhere.
|
|
1863
1863
|
|
|
1864
|
-
## Error handling — else: blocks
|
|
1864
|
+
## Error handling — else: blocks and op-level (fallback:) values
|
|
1865
1865
|
|
|
1866
1866
|
Skillscript has no try/catch — error handling is authored. Two runtime mechanisms (local to global) plus a structural discipline that prevents failure outright.
|
|
1867
1867
|
|
|
@@ -1910,7 +1910,7 @@ Pre-bind defaults (`$set`) for every var the template/downstream reads, then gat
|
|
|
1910
1910
|
|
|
1911
1911
|
## Deprecated: `# OnError:` — parsed but not wired
|
|
1912
1912
|
|
|
1913
|
-
A skill-level `# OnError: <fallback-skill>` header parses, but it is **not wired in the current runtime: the named fallback never fires.** A skill that relies on it has, in effect, no error handling — so don't use it. Use a target-level `else:` handler (or an op-level `(fallback:)`) instead. Removal of the header is planned for a
|
|
1913
|
+
A skill-level `# OnError: <fallback-skill>` header parses, but it is **not wired in the current runtime: the named fallback never fires.** A skill that relies on it has, in effect, no error handling — so don't use it. Use a target-level `else:` handler (or an op-level `(fallback:)`) instead. Removal of the header is planned for a post-launch release (after 0.30); until then it is inert, not a compile error. To recover at the whole-skill level, wrap the body in a target with an `else:` that dispatches your recovery skill:
|
|
1914
1914
|
|
|
1915
1915
|
```
|
|
1916
1916
|
run:
|
|
@@ -2565,5 +2565,5 @@ When any of these primitives ship, the relevant grammar moves into its canonical
|
|
|
2565
2565
|
|
|
2566
2566
|
---
|
|
2567
2567
|
|
|
2568
|
-
*Rendered from `skillscript/skillscript-language-reference` — 2026-07-
|
|
2568
|
+
*Rendered from `skillscript/skillscript-language-reference` — 2026-07-10 13:59 EDT*
|
|
2569
2569
|
*Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillscript-runtime",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.3",
|
|
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>",
|