skillscript-runtime 0.16.4 → 0.16.5
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/dist/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +14 -0
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/http-mcp.d.ts +63 -0
- package/dist/connectors/http-mcp.d.ts.map +1 -0
- package/dist/connectors/http-mcp.js +243 -0
- package/dist/connectors/http-mcp.js.map +1 -0
- package/dist/connectors/index.d.ts +2 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +116 -0
- package/dist/lint.js.map +1 -1
- package/dist/testing/conformance.d.ts +55 -0
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +150 -0
- 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 +6 -1
- package/dist/testing/index.js.map +1 -1
- package/docs/adopter-playbook.md +21 -4
- package/docs/language-reference.md +159 -39
- package/examples/connectors/McpConnectorTemplate/README.md +7 -6
- package/examples/onboarding-scaffold/connectors.json +13 -1
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/examples/skillscripts/morning-brief.skill.md +2 -2
- package/package.json +1 -1
|
@@ -14,6 +14,10 @@ These are features designed or anticipated but not yet implemented in the curren
|
|
|
14
14
|
- **`while CONDITION:` loops** — today's iteration is `foreach IDENT in EXPR:` only. While loops are planned for ad-hoc orchestration patterns ("loop until response contains 'done'").
|
|
15
15
|
- **Arithmetic in `$set`** — today accepts literals + `${VAR}` interpolation; no `+ - * /` operators. Planned alongside `while` for turn counters and orchestration bookkeeping.
|
|
16
16
|
|
|
17
|
+
## Array aggregation primitives
|
|
18
|
+
|
|
19
|
+
`|max`, `|min`, `|sum`, `|reduce` pipe-filters over arrays. Today the language tops out at "shape one record" — aggregating across an array requires `foreach` accumulator ceremony. Warm-agent Phase 3 weather skill (2026-06-01) couldn't do `Math.max(hourly.chanceofrain)` without the workaround. Planned as a design question: is `foreach` the deliberate ceiling for aggregation, or do we add primitives?
|
|
20
|
+
|
|
17
21
|
## Strings
|
|
18
22
|
|
|
19
23
|
- **Multi-line / heredoc string literals** — today's `emit(text="...")` accepts single-line strings or `\n`-escaped multi-line. Planned: Python-style triple-quote `emit(text="""...""")` for ad-hoc prose blocks in template-kind skills.
|
|
@@ -48,10 +52,6 @@ $set NAME = value scope=session
|
|
|
48
52
|
|
|
49
53
|
Scopes: skill-local (persists across fires of this skill, not visible to other skills), agent-global (visible to all skills of the same agent), session (alive for the duration of the current session, cleared at session end). Backed by a configured data-records connector.
|
|
50
54
|
|
|
51
|
-
## Per-skill / per-op timeouts
|
|
52
|
-
|
|
53
|
-
`# Timeout:` skill-level header + per-op `timeoutSeconds=N` kwarg + runtime defaults. Hung dispatches today have no timeout cap.
|
|
54
|
-
|
|
55
55
|
## Sensors as a language category
|
|
56
56
|
|
|
57
57
|
Distinct from triggers. Sensors are continuous channels the agent reads but doesn't emit on. Planned syntax:
|
|
@@ -86,6 +86,7 @@ Most "right time" reasoning is relative, not wall-clock.
|
|
|
86
86
|
- **Channel/locality awareness** — `$(CHANNEL_TYPE)`, `$(CHANNEL_PRIVACY)` ambient refs for routing decisions
|
|
87
87
|
- **Introspection primitives** — `$(PROMPT_CONTEXT.size)`, `$(SKILLS_FIRED_RECENTLY.last-1h)`, `$(SELF.confidence-trend)`
|
|
88
88
|
- **Capability declarations** — `# Requires-Capabilities: sensors=[mic, camera], tools=[...]` (audit surface for operators)
|
|
89
|
+
- **`unknown-llm-arg` lint** — typo-catch for `$ llm` kwargs (today, unknown kwargs pass through silently to the connector). Sibling to the older `unknown-retrieval-arg` rule. Sharpening pass on `$ llm` typed-contract validation.
|
|
89
90
|
|
|
90
91
|
## When the language extends, this section shrinks
|
|
91
92
|
|
|
@@ -214,7 +215,7 @@ The following identifiers are reserved and cannot be used as variable names, tar
|
|
|
214
215
|
|
|
215
216
|
**Mutation statements:** `$set`, `$append`
|
|
216
217
|
|
|
217
|
-
**Runtime-intrinsic op names:** `emit`, `notify`, `
|
|
218
|
+
**Runtime-intrinsic op names:** `emit`, `notify`, `inline`, `execute_skill`, `shell`, `file_read`, `file_write` (the closed function-call list; see Ops Reference)
|
|
218
219
|
|
|
219
220
|
**Control flow:** `default`, `needs`, `if`, `elif`, `else`, `foreach`, `in`, `not`, `unsafe`
|
|
220
221
|
|
|
@@ -284,7 +285,7 @@ The op surface is three classes, each with its own grammar and resolution path.
|
|
|
284
285
|
|---|---|---|
|
|
285
286
|
| **Mutation statements** | `$set VAR = value`, `$append VAR <value>` | Reserved keywords (parser dispatches directly). |
|
|
286
287
|
| **Runtime-intrinsic function-calls** | `verb(kwarg=value, ...) [-> BINDING]` | Closed built-in list (below). Unknown verb → tier-1 `unknown-runtime-op`. |
|
|
287
|
-
| **External MCP dispatch** | `$ <connector>[
|
|
288
|
+
| **External MCP dispatch** | `$ <connector>.<tool> kwarg=value, ... [-> BINDING]` (substrate-specific) or `$ <tool> kwarg=value, ... [-> BINDING]` (typed-contract only) | `connectors.json` resolution at compile. Unknown connector → tier-1 `unknown-connector`. See External MCP dispatch subsection below for canonical form rules. |
|
|
288
289
|
|
|
289
290
|
The `$` prefix is information-bearing: it marks **state-affecting ops** (mutation OR external dispatch). Function-call shape marks **language-intrinsic ops the runtime knows directly**. Parse-time discrimination is unambiguous — three grammars, three resolution paths, zero overlap.
|
|
290
291
|
|
|
@@ -359,7 +360,7 @@ Closed list of language-intrinsic ops the runtime knows directly. Each is a func
|
|
|
359
360
|
| `file_read` | `file_read(path="...") -> R` | required | Read a file at `path`; binds string contents. |
|
|
360
361
|
| `file_write` | `file_write(path="...", content="...")` | none | Write `content` to `path`. `mkdir -p` semantics for parent directories. Mutation-classified. |
|
|
361
362
|
|
|
362
|
-
**Unknown op name** → tier-1 lint `unknown-runtime-op` with remediation pointing at MCP dispatch: "if this is an external tool, use `$
|
|
363
|
+
**Unknown op name** → tier-1 lint `unknown-runtime-op` with remediation pointing at MCP dispatch: "if this is an external tool, use `$ <connector>.<tool> args -> R`."
|
|
363
364
|
|
|
364
365
|
### `emit` — delivery-channel append
|
|
365
366
|
|
|
@@ -389,10 +390,6 @@ Synchronous alert to a named agent via wired AgentConnector(s). **Contrast with
|
|
|
389
390
|
|
|
390
391
|
Returns ACK `{agent, dispatched: [{connector, ok, error?}]}` — fire-and-forget callers ignore the binding; check-delivery callers inspect ACK.
|
|
391
392
|
|
|
392
|
-
### Removed in v0.16.0: `ask` runtime intrinsic
|
|
393
|
-
|
|
394
|
-
`ask(prompt="...")` was removed because it conflated two unrelated concerns: (1) surfacing a question to a user, which requires an interactive channel the runtime can't guarantee for cron/event-fired skills, and (2) acting as a mutation gate. The mutation-gate role is now covered by per-op `approved="reason"` kwarg + skill-level `# Autonomous: true`. For input-collection workflows, use `emit(text="...")` and have the caller handle the round-trip.
|
|
395
|
-
|
|
396
393
|
### `shell` — sandboxed or unsafe shell exec
|
|
397
394
|
|
|
398
395
|
**Sandboxed default:**
|
|
@@ -424,7 +421,7 @@ file_read(path="/tmp/state.json") -> STATE
|
|
|
424
421
|
file_write(path="/tmp/report.md", content="${REPORT}", approved="nightly sweep deliverable")
|
|
425
422
|
```
|
|
426
423
|
|
|
427
|
-
`file_read` is read-only (always allowed). `file_write` is mutation-classified — requires `# Autonomous: true` declaration on the skill OR per-call `approved="..."` kwarg
|
|
424
|
+
`file_read` is read-only (always allowed). `file_write` is mutation-classified — requires `# Autonomous: true` declaration on the skill OR per-call `approved="..."` kwarg. `mkdir -p` semantics for the parent directory.
|
|
428
425
|
|
|
429
426
|
`unconfirmed-mutation` lint enforces the mutation-classification rule.
|
|
430
427
|
|
|
@@ -432,11 +429,11 @@ file_write(path="/tmp/report.md", content="${REPORT}", approved="nightly sweep d
|
|
|
432
429
|
|
|
433
430
|
```
|
|
434
431
|
brief:
|
|
435
|
-
$ llm prompt="${VOICE_RULES} Now write a one-line status:"
|
|
432
|
+
$ llm prompt="${VOICE_RULES} Now write a one-line status:" -> RESULT
|
|
436
433
|
inline(skill="voice-rules")
|
|
437
434
|
```
|
|
438
435
|
|
|
439
|
-
Inlines an Approved `# Type: data` skill into the host skill's compiled artifact at the call site. Resolved at `compile()` time; the data skill's `content_hash` is recorded in the host's provenance. `
|
|
436
|
+
Inlines an Approved `# Type: data` skill into the host skill's compiled artifact at the call site. Resolved at `compile()` time; the data skill's `content_hash` is recorded in the host's provenance. `skillscript audit` detects stale recompiles when a referenced data skill changes.
|
|
440
437
|
|
|
441
438
|
See Composition section for the distinction between `inline` (compile-time), `execute_skill` (in-skill runtime call), and dispatched skills.
|
|
442
439
|
|
|
@@ -455,43 +452,165 @@ Runtime-resolved against the SkillStore. Recursion-depth-guarded (default 10).
|
|
|
455
452
|
|
|
456
453
|
Calls a tool through a configured connector. Connector name resolves against `connectors.json`. Output binds via optional `-> VAR`. Adopter-side contract details + connector wiring conventions live in the adopter playbook.
|
|
457
454
|
|
|
458
|
-
###
|
|
455
|
+
### Dispatch form
|
|
456
|
+
|
|
457
|
+
**Substrate-specific tools use named (dotted) form:**
|
|
459
458
|
|
|
460
|
-
|
|
459
|
+
```
|
|
460
|
+
$ <connector>.<tool> kwarg=value, ... [-> R]
|
|
461
|
+
```
|
|
461
462
|
|
|
462
|
-
|
|
463
|
+
The text before the dot is the connector name (must match an entry in `connectors.json`); the rest is the tool name + args. Named form is canonical for any tool whose semantics are substrate-specific — adopter MCP servers (`amp_*`, `github_*`, `linear_*`, etc.).
|
|
463
464
|
|
|
464
465
|
```
|
|
465
|
-
$
|
|
466
|
-
$
|
|
467
|
-
$
|
|
466
|
+
$ amp.amp_check_mailbox limit=20 -> MAIL
|
|
467
|
+
$ amp.amp_write_memory summary="..." domain_tags=["x"] vault="private" -> ACK
|
|
468
|
+
$ github.search_issues query="repo:acme/foo state:open" -> ISSUES
|
|
469
|
+
$ linear.find_issues filter="project:INFRA" -> TICKETS
|
|
468
470
|
```
|
|
469
471
|
|
|
470
|
-
|
|
472
|
+
The skill body literally names its substrate edges — reading the body tells you which MCP servers it depends on, with no `primary` config indirection.
|
|
473
|
+
|
|
474
|
+
**Typed-contract tools use bare form:**
|
|
471
475
|
|
|
472
476
|
```
|
|
473
|
-
$
|
|
474
|
-
$ data_read.query query="..." -> M
|
|
477
|
+
$ <tool> kwarg=value, ... [-> R]
|
|
475
478
|
```
|
|
476
479
|
|
|
477
|
-
|
|
480
|
+
Bare form is reserved for ops with a typed runtime contract — the substrate variance is invisible to the skill body because the runtime dispatches through the contract.
|
|
478
481
|
|
|
479
|
-
|
|
482
|
+
```
|
|
483
|
+
$ data_read mode=fts query="${TOPIC}" limit=5 -> RESULTS
|
|
484
|
+
$ data_write content="${SUMMARY}" tags=["nightly"] -> ACK
|
|
485
|
+
$ skill_read name="hello-world" -> S
|
|
486
|
+
$ skill_write name="child" source="..." overwrite=true -> W
|
|
487
|
+
$ json_parse ${RAW_JSON} -> PARSED
|
|
488
|
+
$ llm prompt="..." -> R
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Typed-contract ops (`data_*`, `skill_*`, `json_parse`, `llm`) auto-wire from the adopter's `substrate.local_model` / `substrate.data_store` / `substrate.skill_store` config — the skill body doesn't know whether it's running against SQLite or Postgres or Pinecone. That's the point of the typed contract.
|
|
492
|
+
|
|
493
|
+
**Lint rules:**
|
|
494
|
+
- `unknown-connector` (tier-1) — `$ x.y` where `x` isn't wired. Lists wired connector names.
|
|
495
|
+
- `bare-mcp-call` (tier-1) — `$ tool_x` where `tool_x` isn't a typed-contract op AND no `primary` connector is wired. Remediation: qualify as `$ <connector>.tool_x`.
|
|
496
|
+
- `unverified-qualified-tool` (advisory) — `$ x.y` where connector `x` doesn't declare its tool surface statically. Runtime will resolve at call time.
|
|
497
|
+
|
|
498
|
+
### Universal op-level kwargs
|
|
499
|
+
|
|
500
|
+
Four surfaces apply to every `$` dispatch regardless of tool. The runtime intercepts these before forwarding the remaining kwargs to the connector.
|
|
501
|
+
|
|
502
|
+
**`timeout=N`** — Per-op timeout in **seconds**. Integer literal or `${VAR}` ref. Resolution chain (most-specific wins):
|
|
503
|
+
|
|
504
|
+
1. Per-op `timeout=N` kwarg
|
|
505
|
+
2. Skill-level `# Timeout: N` header
|
|
506
|
+
3. Absolute context default (runtime config)
|
|
507
|
+
4. Built-in default
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
$ llm prompt="..." timeout=30 -> R
|
|
511
|
+
$ amp.amp_olsen_task task_type="scan" timeout=120 -> DISPATCH
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**`approved="<reason>"`** — Author-intent marker for the mutation-gate lint. Any non-empty string satisfies. Extracted at runtime; **not forwarded to the connector**. The value is required but not parsed semantically — presence is the signal.
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
$ data_write content="${REPORT}" tags=["oncall"] approved="morning roundup" -> ACK
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**`(fallback: "value")` trailer** — Fires on dispatch throw OR empty bound value (empty string after trim, empty array, null/undefined). Coerce-on-bind: the fallback value binds to the output var transparently, downstream targets need no conditional. Permissive value parsing — bare identifiers, quoted strings, bracketed array literals all accepted.
|
|
521
|
+
|
|
522
|
+
Note: an envelope object like `{items: []}` is a non-empty object and does NOT trigger the fallback even though its contained array is empty. To handle envelope-empty downstream, test the contained collection (`if ${R.items|length} == "0":`) or apply a filter (`${R.items|fallback:[]}`).
|
|
523
|
+
|
|
524
|
+
```
|
|
525
|
+
$ llm prompt="Classify: ${INPUT}" -> VERDICT (fallback: "unknown")
|
|
526
|
+
$ amp.amp_query_memories query="${TOPIC}" -> RESULTS (fallback: [])
|
|
527
|
+
$ ticketing.search query="..." -> ISSUES (fallback: "search-unavailable")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**`-> R` binding** — Optional output capture. Result string binds to `R` and to `${target}.output`. Omit when the dispatch is fire-and-forget.
|
|
531
|
+
|
|
532
|
+
**Kwarg validation for typed-contract ops.** Two sibling lints close the kwarg surface — provider-API kwargs (e.g., `temperature=0.7` on `$ llm`) silently dropped by the bridge get caught at parse time:
|
|
533
|
+
|
|
534
|
+
- `unknown-llm-arg` (tier-2) — `$ llm` carries a kwarg outside the canonical surface (`prompt`/`maxTokens`/`model`/`timeout`/`approved`/`fallback`). `LocalModel.run()` only consumes `{maxTokens, model}`; anything else is silently dropped at the bridge.
|
|
535
|
+
- `unknown-data-read-arg` (tier-2) — `$ data_read` carries a kwarg outside the canonical surface (`mode`/`query`/`limit`/`connector`/`fallback`/`domain_tags`/`filters`/`min_confidence`).
|
|
536
|
+
|
|
537
|
+
For named-form dispatch (`$ <connector>.<tool>`) the connector's `staticTools()` surface + per-tool kwarg validation is the discipline boundary — adopter MCP servers each define their own.
|
|
538
|
+
|
|
539
|
+
### Typed-contract op surfaces
|
|
540
|
+
|
|
541
|
+
Canonical kwarg surfaces for each typed-contract bare-form op. These are the ops the runtime auto-wires from `substrate.local_model` / `substrate.data_store` / `substrate.skill_store` config.
|
|
542
|
+
|
|
543
|
+
#### `$ llm`
|
|
544
|
+
|
|
545
|
+
Routes through the wired LocalModel.
|
|
546
|
+
|
|
547
|
+
| Kwarg | Required | Notes |
|
|
548
|
+
|---|---|---|
|
|
549
|
+
| `prompt="..."` | yes | Non-empty string. Substitution-resolved at runtime. |
|
|
550
|
+
| `maxTokens=N` | no | Positive integer (number or numeric string). Forwarded as `runOpts.maxTokens`. |
|
|
551
|
+
| `model="X"` | no | Per-call model selection. Resolves against registered LocalModel aliases via `registry.getLocalModel(X)`; falls through to the default LocalModel with `model=X` passed as an upstream hint (e.g., Ollama tag) when X doesn't resolve. See `unknown-llm-model` lint below. |
|
|
552
|
+
|
|
553
|
+
Plus the universal op-level kwargs (`timeout`, `approved`, `(fallback:)`, `-> R`).
|
|
554
|
+
|
|
555
|
+
Canonical shape:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
$ llm prompt="..." [maxTokens=N] [model="<alias-or-tag>"] [timeout=N] [approved="..."] -> R [(fallback: "...")]
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
The wired LocalModel determines which underlying model serves the request. Adopters with multiple registered LocalModels (e.g., `qwen` + `gemma`) can target a specific one via `model="qwen"` from the skill body. The `unknown-llm-model` lint (tier-2) validates the value against both registered alias names AND each LocalModel's `manifest().manifest.models_available` — substrate-aware typo-catch, since manifest data is exposed via `runtime_capabilities`.
|
|
562
|
+
|
|
563
|
+
#### `$ data_read` / `$ data_write`
|
|
564
|
+
|
|
565
|
+
Route through the wired DataStore. Surface depends on the connector contract — see the adopter playbook for the canonical DataStore contract reference. Common shape:
|
|
480
566
|
|
|
481
567
|
```
|
|
482
|
-
$ ticketing_search query="project:INFRA state:Open" limit=20 -> ISSUES
|
|
483
|
-
$ llm prompt="Classify: ${INPUT}" -> VERDICT
|
|
484
568
|
$ data_read mode=fts query="${TOPIC}" limit=5 -> RESULTS
|
|
485
|
-
$ data_write content="${SUMMARY}"
|
|
569
|
+
$ data_write content="${SUMMARY}" tags=["nightly"] approved="..." -> ACK
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### `$ skill_read` / `$ skill_write`
|
|
573
|
+
|
|
574
|
+
Route through the wired SkillStore.
|
|
575
|
+
|
|
576
|
+
```
|
|
577
|
+
$ skill_read name="hello-world" -> S
|
|
578
|
+
$ skill_write name="child" source="..." overwrite=true approved="..." -> W
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### `$ json_parse`
|
|
582
|
+
|
|
583
|
+
Runtime-intrinsic parser. Single positional value (the raw JSON string).
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
$ json_parse ${RAW_JSON} -> PARSED
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Worked examples
|
|
590
|
+
|
|
591
|
+
```
|
|
592
|
+
$ amp.amp_olsen_task task_type="scan" -> DISPATCH
|
|
593
|
+
$ amp.amp_query_memories query="recent activity" limit=5 -> RECENT
|
|
594
|
+
$ llm prompt="Classify: ${INPUT}" timeout=30 -> VERDICT
|
|
595
|
+
$ llm prompt="Summarize: ${TEXT}" model="qwen" maxTokens=500 -> SUMMARY
|
|
596
|
+
$ data_read mode=fts query="${TOPIC}" limit=5 -> RESULTS (fallback: [])
|
|
597
|
+
$ data_write content="${SUMMARY}" tags=["oncall"] approved="morning roundup, 2026-05-25" -> ACK
|
|
598
|
+
$ ticketing.search query="project:INFRA state:Open" limit=20 -> ISSUES
|
|
486
599
|
```
|
|
487
600
|
|
|
488
601
|
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[]`.
|
|
489
602
|
|
|
490
|
-
**Substrate-neutrality.**
|
|
603
|
+
**Substrate-neutrality.** Typed-contract bare ops (`$ llm`, `$ data_read`, `$ data_write`, `$ skill_*`, `$ json_parse`) are not reserved built-ins — they're typed-contract bridges the adopter wires through `substrate.local_model` / `substrate.data_store` / `substrate.skill_store` in `skillscript.config.json`. Substrate-specific tools (`$ amp.*`, `$ github.*`, etc.) are whatever the adopter declares in `connectors.json`. For external MCP servers, three bundled wiring paths cover the common cases:
|
|
604
|
+
|
|
605
|
+
- `HttpMcpConnector` — declarative wiring for any Streamable HTTP MCP server (JSON-RPC over HTTP + SSE). No subprocess. Adopters declare instances by class name in `connectors.json`.
|
|
606
|
+
- `RemoteMcpConnector` — stdio bridging for MCPs distributed as spawnable binaries (YouTrack, GitHub, Linear when run locally) or HTTP MCPs adapted via `npx mcp-remote ... --sse`.
|
|
607
|
+
- Custom fork (`examples/connectors/McpConnectorTemplate/`) — when the wire shape needs behavior the bundled connectors don't expose.
|
|
608
|
+
|
|
609
|
+
See the adopter playbook for the substrate config reference + the full Case 2 tradeoff.
|
|
491
610
|
|
|
492
|
-
**
|
|
611
|
+
**Discovery surface.** `runtime_capabilities` (MCP tool) exposes the registered substrate state. Every entry across all four substrate slots (SkillStore / DataStore / LocalModel / McpConnector) carries its instance `manifest()` payload alongside the static features. Three observable states per entry: working `manifest:{...}`, runtime failure `manifest:null, manifest_error:"..."`, structural absence `manifest:null, manifest_unsupported:true` (AgentConnector only — the contract has no `manifest()` method). The bridge `wraps` convention re-exposes the underlying substrate's full manifest, so adopters reading the discovery surface see the full bound state without traversing multiple entries.
|
|
493
612
|
|
|
494
|
-
**Unquoted-substitution lint** (`unquoted-substitution-in-kwarg-value`, tier-2): fires when `$
|
|
613
|
+
**Unquoted-substitution lint** (`unquoted-substitution-in-kwarg-value`, tier-2): fires when `$ x.y key=${VAR}` has unquoted `${VAR}` AND the var's binding origin is "suspect" (`# Vars:` default with whitespace, `$set` with whitespace, op output, foreach iterator). Closes the silent-arg-truncation footgun where the MCP arg parser whitespace-splits substituted values. Remediation: wrap as `key="${VAR}"`.
|
|
495
614
|
|
|
496
615
|
---
|
|
497
616
|
|
|
@@ -504,12 +623,12 @@ Mutation ops require an authorization signal. The signal is per-op, not a mode b
|
|
|
504
623
|
- `$ data_write ...` and any MCP connector entry declared `"mutating": true` in `connectors.json`
|
|
505
624
|
- `shell(command=..., unsafe=true)` (always mutation-classified)
|
|
506
625
|
- `shell(command=...)` with destructive verb (rm, mv, dd, mkfs, etc. — heuristic list)
|
|
507
|
-
- `$ <tool>` matching the mutating-verb regex
|
|
626
|
+
- `$ <connector>.<tool>` matching the mutating-verb regex
|
|
508
627
|
|
|
509
628
|
**Read-only ops (always allowed, no authorization needed):**
|
|
510
|
-
- `file_read`, `emit`, `notify`, `
|
|
629
|
+
- `file_read`, `emit`, `notify`, `inline`, `execute_skill`
|
|
511
630
|
- `shell(command=...)` with read-only verb
|
|
512
|
-
- `$ <connector
|
|
631
|
+
- `$ <connector>.<tool>` against tools declared `mutating: false` (or unspecified, default false for query-shaped tools)
|
|
513
632
|
- `$set`, `$append`
|
|
514
633
|
|
|
515
634
|
**Authorization signals (either suffices):**
|
|
@@ -524,7 +643,7 @@ Mutation ops require an authorization signal. The signal is per-op, not a mode b
|
|
|
524
643
|
|
|
525
644
|
deliver:
|
|
526
645
|
file_write(path="/tmp/sweep.md", content="${REPORT}") # no approved= needed
|
|
527
|
-
$ data_write content="${REPORT}"
|
|
646
|
+
$ data_write content="${REPORT}" tags=["oncall"] # no approved= needed
|
|
528
647
|
```
|
|
529
648
|
|
|
530
649
|
```
|
|
@@ -551,7 +670,8 @@ deliver:
|
|
|
551
670
|
| Runtime-intrinsic | `shell` | `shell(command="...", [unsafe=true], [approved="..."]) -> R` | optional |
|
|
552
671
|
| Runtime-intrinsic | `file_read` | `file_read(path="...") -> R` | required |
|
|
553
672
|
| Runtime-intrinsic | `file_write` | `file_write(path="...", content="...", [approved="..."])` | none |
|
|
554
|
-
| External MCP | `$ <connector>` | `$ <
|
|
673
|
+
| External MCP — substrate-specific | `$ <connector>.<tool>` | `$ <connector>.<tool> kwarg=value, ... [timeout=N] [approved="..."] [-> R] [(fallback: "...")]` | optional |
|
|
674
|
+
| External MCP — typed-contract | `$ <tool>` | `$ <tool> kwarg=value, ... [timeout=N] [approved="..."] [-> R] [(fallback: "...")]` (typed-contract ops only: `data_*`, `skill_*`, `json_parse`, `llm`) | optional |
|
|
555
675
|
|
|
556
676
|
## Variable resolution — ${VAR} canonical, substitution + ambient refs + # Requires: cascade
|
|
557
677
|
|
|
@@ -1576,7 +1696,7 @@ The Template-kind skill is the canonical static shape — its `# Output: templat
|
|
|
1576
1696
|
|
|
1577
1697
|
## Dynamic skill
|
|
1578
1698
|
|
|
1579
|
-
A dynamic skill requires the Skillscript runtime to execute. The runtime walks the dispatch DAG, fires `$` ops against wired connectors, runs runtime-intrinsic ops (`emit`, `notify`, `
|
|
1699
|
+
A dynamic skill requires the Skillscript runtime to execute. The runtime walks the dispatch DAG, fires `$` ops against wired connectors, runs runtime-intrinsic ops (`emit`, `notify`, `shell`, `file_read`, `file_write`, `execute_skill`), and threads outputs through variable bindings.
|
|
1580
1700
|
|
|
1581
1701
|
Dynamic skills are the default for:
|
|
1582
1702
|
- **Autonomous workflows** — cron-fired Headless skills that fetch, reason, and emit
|
|
@@ -1598,7 +1718,7 @@ The axes are independent. A skill author can produce any combination.
|
|
|
1598
1718
|
|
|
1599
1719
|
A `# Portability: static | dynamic` frontmatter header would declare the skill's intended execution model. The compiler would lint-check that the skill's op set is consistent with the declaration:
|
|
1600
1720
|
|
|
1601
|
-
- `# Portability: static` → no `$` dispatch ops permitted; no side-effect runtime-intrinsics (`shell`, `file_write`, `
|
|
1721
|
+
- `# Portability: static` → no `$` dispatch ops permitted; no side-effect runtime-intrinsics (`shell`, `file_write`, `notify`, `execute_skill`); only the static-safe set (`emit`, `$set`, `$append`, `inline()`, conditionals, iteration)
|
|
1602
1722
|
- `# Portability: dynamic` (or unset, the default) → any op permitted
|
|
1603
1723
|
|
|
1604
1724
|
A new compile mode `compile_skill({source, mode: "static"})` would render only the portable artifact, refusing skills that depend on runtime dispatch.
|
|
@@ -1922,5 +2042,5 @@ Hung dispatches hang the skill without explicit timeout configuration. Lean: ski
|
|
|
1922
2042
|
|
|
1923
2043
|
---
|
|
1924
2044
|
|
|
1925
|
-
*Rendered from `skillscript/skillscript-language-reference` — 2026-
|
|
2045
|
+
*Rendered from `skillscript/skillscript-language-reference` — 2026-06-02 16:30 EDT*
|
|
1926
2046
|
*Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A skeleton `McpConnector` implementation for adopters writing their own. Not runnable; every method throws a `TODO` error. Copy this directory, rename, fill in the substrate-specific work.
|
|
4
4
|
|
|
5
|
-
**Most adopters don't need this template.**
|
|
5
|
+
**Most adopters don't need this template.** Bundled impls already cover the common cases:
|
|
6
6
|
|
|
7
7
|
| Bundled impl | What it covers |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `
|
|
9
|
+
| `HttpMcpConnector` | Speaks Streamable HTTP MCP (JSON-RPC over HTTP + SSE) directly — no subprocess. Works against any compliant HTTP MCP server (Anthropic's hosted MCP, GitHub MCP, Linear MCP, etc.). JSON-configurable via `connectors.json`. |
|
|
10
|
+
| `RemoteMcpConnector` | Stdio bridging to remote MCP servers (`npx mcp-remote ...`). For MCPs distributed as binaries you spawn (YouTrack, GitHub, Linear when run locally) or HTTP MCPs that need an HTTPS-to-stdio adapter. JSON-configurable. |
|
|
10
11
|
| `CallbackMcpConnector` | Wraps a JS function. Test rigs + embedder-wired transports where the dispatch is local code. |
|
|
11
12
|
| `LocalModelMcpConnector` | Bridges a registered `LocalModel` as `$ llm prompt=...`. Auto-wired when `substrate.local_model` is set. |
|
|
12
13
|
| `DataStoreMcpConnector` | Bridges a registered `DataStore` as `$ data_read mode=...` + `$ data_write content=...`. Auto-wired when `substrate.data_store` is set. |
|
|
@@ -14,15 +15,15 @@ A skeleton `McpConnector` implementation for adopters writing their own. Not run
|
|
|
14
15
|
|
|
15
16
|
**Fork this template only when none of those fit** — e.g.:
|
|
16
17
|
|
|
17
|
-
- Direct HTTP MCP (JSON-RPC over HTTP, no child process) — for now; check the bundled `McpConnector` classes before forking, as a bundled `HttpMcpConnector` is on the near-term roadmap
|
|
18
18
|
- WebSocket MCP
|
|
19
19
|
- In-process MCP (call methods directly without IPC)
|
|
20
|
-
- Custom transport that doesn't match stdio framing
|
|
20
|
+
- Custom transport that doesn't match stdio framing or Streamable HTTP
|
|
21
21
|
- Cross-thread / worker-pool dispatch
|
|
22
|
+
- An HTTP MCP server that diverges from the spec (custom auth handshake, non-SSE response framing, tool-name normalization, etc.) in ways `HttpMcpConnector` doesn't expose
|
|
22
23
|
|
|
23
|
-
If you're trying to wire a remote stdio MCP server like YouTrack or GitHub, **you want `RemoteMcpConnector` in `connectors.json`**, not this template. See `connectors.json.example` for the wiring pattern.
|
|
24
|
+
If you're trying to wire a remote stdio MCP server like YouTrack or GitHub run locally, **you want `RemoteMcpConnector` in `connectors.json`**, not this template. See `connectors.json.example` for the wiring pattern.
|
|
24
25
|
|
|
25
|
-
If you're trying to wire a Streamable HTTP MCP server (
|
|
26
|
+
If you're trying to wire a Streamable HTTP MCP server (Anthropic's hosted MCP, GitHub MCP via HTTPS, etc.), **declare an `HttpMcpConnector` instance in `connectors.json`** — no subprocess, no implementation. See `docs/adopter-playbook.md` §Case 2 path (b) for the full shape.
|
|
26
27
|
|
|
27
28
|
## Forking workflow
|
|
28
29
|
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Onboarding scaffold connectors.json — wires example MCP servers an adopter might add alongside the bundled bridges. Most onboarding deployments don't need anything here; the file-backed data store + OpenAI LLM + tmux-shell wiring lives in bootstrap.ts. This file is the place for adopter-specific MCP servers (your in-house tools, a YouTrack instance, a custom agent API, etc.).",
|
|
3
3
|
|
|
4
|
-
"_comment_transport": "
|
|
4
|
+
"_comment_transport": "Two bundled paths for MCP transport: (1) `HttpMcpConnector` speaks Streamable HTTP MCP directly — no subprocess, lowest overhead, works against any compliant Streamable HTTP MCP server. (2) `RemoteMcpConnector` spawns a child process for stdio MCP servers — also bridges Streamable HTTP via `mcp-remote https://endpoint/sse` when you need an HTTPS-to-stdio adapter. Pick (1) by default for HTTP MCP; (2) for native-stdio servers (community MCPs commonly distributed as binaries you spawn). See docs/adopter-playbook.md §Case 2 for the full tradeoff.",
|
|
5
|
+
|
|
6
|
+
"_example_http_mcp": {
|
|
7
|
+
"class": "HttpMcpConnector",
|
|
8
|
+
"config": {
|
|
9
|
+
"endpoint": "https://your-mcp-server.example.com/",
|
|
10
|
+
"headers": {
|
|
11
|
+
"Authorization": "Bearer ${MY_API_TOKEN}",
|
|
12
|
+
"X-Agent-ID": "${AGENT_ID}"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"allowed_tools": ["search", "fetch"]
|
|
16
|
+
},
|
|
5
17
|
|
|
6
18
|
"_example_remote_mcp": {
|
|
7
19
|
"class": "RemoteMcpConnector",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Skill: morning-brief
|
|
2
|
-
# Status: Approved v1:
|
|
3
|
-
# Description: Compose a daily morning brief from calendar, mailbox, and overnight data writes when the cron trigger fires at 7am. Delivers via the agent: lifecycle hook to the receiving agent, who decides whether to surface to Slack / Discord / etc.
|
|
2
|
+
# Status: Approved v1:a08d0f21
|
|
3
|
+
# Description: Compose a daily morning brief from calendar, mailbox, and overnight data writes when the cron trigger fires at 7am. Delivers via the agent: lifecycle hook to the receiving agent, who decides whether to surface to Slack / Discord / etc. The `model=qwen` value below is a representative alias — adopters register a LocalModel under whatever name fits their setup (the bundled bootstrap registers one as `default`).
|
|
4
4
|
# Vars: AGENT, BRIEF_HORIZON_HOURS=24
|
|
5
5
|
# Triggers: cron: 0 7 * * *
|
|
6
6
|
# OnError: morning-brief-degraded
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillscript-runtime",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
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>",
|