skillscript-runtime 0.19.13 → 0.19.14

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.
@@ -1495,6 +1495,290 @@ The receiving agent reads `event_type` for routing ("911 — surface now" vs "ro
1495
1495
 
1496
1496
  If `# Output: agent: <name>` fires and the wired AgentConnector throws, delivery routes through `else:` / `# OnError:`; the failure is recorded on `agent_delivery_receipts[]` for the scheduler to log.
1497
1497
 
1498
+ ## Connectors — substrate routing, the five connector types, agent_id resolution
1499
+
1500
+ The substrate-routing ops (`$ connector.tool`, `$ data_read`, `$ llm`) and the agent-bound `# Output:` kinds (`agent:`, `template:`) don't call any specific backend directly. They route through thin connector interfaces. Skill source persistence follows the same pattern via a dedicated contract. This is the programmable surface through which authors compose information topology per skill and per moment. Skills are portable across substrates because the language doesn't bake substrate identity into the source.
1501
+
1502
+ ## Five connector types
1503
+
1504
+ ### MemoryStore
1505
+
1506
+ Routes `$ data_read` retrieval ops. Interface: `MemoryStore.query(filters) → PortableMemory[]`.
1507
+
1508
+ Implementations vary by deployment — a knowledge-substrate-backed store, a SQLite-backed store, a vector-DB-backed store, an in-memory test store. All conform to the `MemoryStore.query` contract and return `PortableMemory[]`.
1509
+
1510
+ ### LocalModel
1511
+
1512
+ Routes `$ llm` local-model ops. Interface: `LocalModel.run(prompt, opts) → string`.
1513
+
1514
+ Default impl wraps a local-model HTTP service (e.g., Ollama). Constructor takes `{ model: string }` (required) — no class-level implicit default. Multiple instances by name in the registry; each backed by a distinct model tag.
1515
+
1516
+ ### McpConnector
1517
+
1518
+ Routes `$ connector.tool` MCP-tool ops. Interface: `McpConnector.call(toolName, args, ctxOverrides?) → unknown`.
1519
+
1520
+ Implementations include adapters wrapping in-process tool dispatch (when the runtime is embedded in a host that already has MCP tools) and HTTP-based MCP clients (when calling out to remote MCP servers). All conform to the `McpConnector.call` contract.
1521
+
1522
+ **Bare-name dispatch.** `$ TOOL` (no connector prefix) routes through the `primary` McpConnector entry in the registry. If no `primary` is wired, the runtime throws `ConnectorNotFoundError` at runtime AND fires tier-1 `unwired-primary-connector` lint at compile time. The remediation diagnostic includes both fix paths: add `primary` to connectors.json, or qualify the op as `$ named.TOOL`. Failing loud is the correct mode for autonomous-pattern skills — a silent stub would let an autonomous skill appear to succeed while doing nothing.
1523
+
1524
+ ### AgentConnector
1525
+
1526
+ Routes the agent-bound `# Output:` kinds — `agent:` (Augmenting) and `template:` (Template) per the skill-kind taxonomy in Section 1. Interface:
1527
+
1528
+ ```typescript
1529
+ interface AgentConnector {
1530
+ list_agents(): Promise<AgentDescriptor[]>;
1531
+ deliver(agent_id: string, payload: DeliveryPayload): Promise<DeliveryReceipt>;
1532
+ wake(agent_id: string, opts?: WakeOpts): Promise<WakeReceipt>;
1533
+ agent_status?(agent_id: string): Promise<AgentStatus>;
1534
+ }
1535
+
1536
+ type DeliveryPayload =
1537
+ | { kind: "augment"; content: string; format?: "text" | "markdown"; source_skill?: string; triggered_by?: TriggerProvenance; delivery_context?: string; templates?: string[] }
1538
+ | { kind: "template"; prompt: string; source_skill?: string; triggered_by?: TriggerProvenance; delivery_context?: string; templates?: string[] };
1539
+
1540
+ type TriggerProvenance = {
1541
+ source: "cron" | "session" | "event" | "agent-event" | "file-watch" | "sensor" | "manual";
1542
+ name: string; // e.g. "0 8 * * *", "session:start", "manual"
1543
+ fired_at_ms: number; // unix ms timestamp
1544
+ };
1545
+
1546
+ type DeliveryReceipt = { delivered_at: number; delivery_id?: string };
1547
+
1548
+ type WakeOpts = {
1549
+ context?: string;
1550
+ when?: "immediate" | number;
1551
+ };
1552
+
1553
+ type WakeReceipt = { woken_at: number; session_id?: string };
1554
+
1555
+ type AgentDescriptor = {
1556
+ agent_id: string;
1557
+ agent_name?: string;
1558
+ capabilities?: ("deliver" | "wake" | "augment" | "template")[];
1559
+ };
1560
+
1561
+ type AgentStatus = "active" | "idle" | "asleep" | "unknown";
1562
+ ```
1563
+
1564
+ The `agent:` Output kind produces a `DeliveryPayload` of kind `augment`; `template:` produces one of kind `template`.
1565
+
1566
+ Two primary verbs (`deliver` + `wake`), one mandatory discovery method (`list_agents`), one optional status method. The contract is substrate-neutral; adopters wire any delivery mechanism behind it:
1567
+
1568
+ | Substrate | `deliver` impl | `wake` impl |
1569
+ |---|---|---|
1570
+ | tmux session | `tmux send-keys` to a pane | `tmux send-keys` with wake prompt |
1571
+ | webhook | POST to `/augment` or `/template` endpoint | POST to `/wake` endpoint |
1572
+ | memory store | write a memory record with delivery tag | write addressed memory + push notification |
1573
+ | file-watch | write to `<path>/augment-<id>.txt` | write to `<path>/wake-<id>.txt` |
1574
+ | chat thread | post to monitored thread | post + @mention |
1575
+ | IPC named pipe | write to delivery pipe | write to wake pipe |
1576
+
1577
+ Default impl `NoOpAgentConnector` logs warnings and resolves; lets the runtime ship without an agent-delivery substrate wired. Adopter impls run the bundled `AgentConnectorConformance` suite to verify their substrate wiring.
1578
+
1579
+ #### DeliveryPayload provenance fields
1580
+
1581
+ Every `deliver` call carries optional provenance the receiving agent uses to disambiguate the source and context of the delivery:
1582
+
1583
+ - `source_skill?: string` — name of the skill that produced this delivery. Lets the receiver attribute the content to a specific authored skill, distinguishing "this is from the stock-monitor skill" from "this is from the news-brief skill."
1584
+ - `triggered_by?: TriggerProvenance` — why the skill fired. Receiver disambiguates cron tick (autonomous), session-start (lifecycle), event-driven (external signal), manual (user-requested), etc. Carries the trigger source + name + unix-ms timestamp.
1585
+ - `delivery_context?: string` — prose explanation of why the agent is being notified and what to do with the content. Populated from the `# Delivery-context:` header (see Section 7).
1586
+ - `templates?: string[]` — list of Template-kind skill names the receiver can fetch as follow-on actions. Populated from the `# Templates:` header (see Section 7).
1587
+
1588
+ The runtime threads these from `ExecuteContext.triggerCtx` (set at dispatch) and `ParsedSkill.deliveryContext` / `ParsedSkill.templates` (set at parse) through to the `deliver` call site.
1589
+
1590
+ #### `agent_id` resolution
1591
+
1592
+ When `# Output: agent:` or `# Output: template:` fires, the runtime resolves the target agent_id via a 2-level chain (first match wins):
1593
+
1594
+ 1. **Explicit name in the `# Output:` line** — `# Output: agent: perry` dispatches to agent_id `perry`.
1595
+ 2. **`${VAR}` compile-time substitution** — `# Output: agent: ${RECIPIENT}` resolves against the resolved inputs map (`# Vars:` defaults + `# Requires:` cascade + caller-supplied `inputs`) at compile time. Caveat: only compile-time inputs resolve here — runtime-bound refs (a target's output var, ambient refs) pass through verbatim and fail at delivery if still unresolved.
1596
+
1597
+ Invocation-context inheritance and a runtime-config `default_agent_id` fallback are NOT implemented: `# Output: agent:` does not auto-inherit the caller's identity, and there is no default-agent fallback. A skill must name its target explicitly or pass it as an input var.
1598
+
1599
+ #### Output-kind classification in the runtime
1600
+
1601
+ The runtime's `TEXT_COERCED_OUTPUT_KINDS` set classifies output kinds by payload shape (text vs structured), not by semantic destination. Membership controls payload coercion; it doesn't bake destination identity into the runtime.
1602
+
1603
+ ### SkillStore
1604
+
1605
+ Routes skill source persistence. Interface:
1606
+
1607
+ ```typescript
1608
+ interface SkillStore {
1609
+ get(name: string): Promise<SkillRecord | null>;
1610
+ write(name: string, body: string): Promise<void>;
1611
+ list(): Promise<SkillDescriptor[]>;
1612
+ delete(name: string): Promise<void>;
1613
+ }
1614
+ ```
1615
+
1616
+ Bundled impls: `FilesystemSkillStore` reads and writes `.skill.md` source plus `.skill` compiled output and `.skill.provenance.json` sidecar in a configured directory; the standard for file-backed deployments. Substrate-specific impls live in adopter packages (memory-backed stores live in the substrate's adapter repo).
1617
+
1618
+ Skill records are infrastructure, not knowledge atoms — adopter impls should treat skills as first-class long-lived records, not as candidates for substrate-level garbage collection.
1619
+
1620
+ ## Capabilities discovery
1621
+
1622
+ All connector types expose `capabilities()` for runtime discovery. Consumers:
1623
+ 1. `# Requires:` matching against the registered set
1624
+ 2. Dynamic queries via `listMemoryStores()` / `listLocalModels()` / `listMcpConnectors()` / `listAgentConnectors()` to pick a connector for the moment
1625
+ 3. Authoring tools that surface the registered set
1626
+
1627
+ ## Multi-instance by design
1628
+
1629
+ Multiple instances of the same connector type are the *normal case*, not the exception.
1630
+
1631
+ ```
1632
+ {
1633
+ primary: MemoryStoreImplA,
1634
+ project: SqliteProjectStore,
1635
+ scratch: InMemoryStore
1636
+ }
1637
+ ```
1638
+
1639
+ ```
1640
+ {
1641
+ default: OllamaLocalModel({model: "gemma2:9b"}),
1642
+ gemma2: OllamaLocalModel({model: "gemma2:9b"}),
1643
+ qwen: OllamaLocalModel({model: "qwen2.5:7b"})
1644
+ }
1645
+ ```
1646
+
1647
+ ```
1648
+ {
1649
+ primary: PrimaryMcpConnector,
1650
+ personal: HttpMcpConnector,
1651
+ project: HttpMcpConnector
1652
+ }
1653
+ ```
1654
+
1655
+ Per-skill resolution against named connectors is first-class; an unnamed lookup returns the configured default. Multiple keys pointing at the same underlying instance configuration are allowed and useful — see the `default`/`gemma2` alias below.
1656
+
1657
+ ## Model selection — choosing among LocalModel instances
1658
+
1659
+ The LocalModel registry holds multiple instances by design. Skill authors choose which to dispatch to via `$ llm model="<name>"`. Two layers of indirection are involved, and the distinction matters for both authoring and adopter configuration:
1660
+
1661
+ 1. **Skillscript name → registered instance.** `$ llm model="qwen"` references the instance keyed `qwen` in the registry. The registry resolves to the configured connector implementation.
1662
+ 2. **Registered instance → underlying model.** Each `OllamaLocalModel` is constructed with the actual model tag (e.g. `qwen2.5:7b`). The skill never sees the tag directly.
1663
+
1664
+ ### Example instance names
1665
+
1666
+ | Name | Underlying model | Notes |
1667
+ | --- | --- | --- |
1668
+ | `default` | `gemma2:9b` | Resolved when `model=` is omitted; alias of `gemma2` |
1669
+ | `gemma2` | `gemma2:9b` | Explicit name; matches the convention below |
1670
+ | `qwen` | `qwen2.5:7b` | Interactive, latency-sensitive |
1671
+
1672
+ `default` and `gemma2` can point at the same `OllamaLocalModel` configuration. The alias exists so skill syntax can match a tier convention ("use gemma2 for batch") rather than the back-compat name (`default`). Skills that write `model="default"` work unchanged; prefer the explicit name.
1673
+
1674
+ ### Convention: model tier by use case
1675
+
1676
+ - **Small classification-class model** (e.g., `gemma2`) for *batch and scan work* — atomization, large-batch classification, anything async or background-scheduled.
1677
+ - **Longer-context dispatch-class model** (e.g., `qwen`) for *interactive verdicts in skills* — single-shot decisions inside an active dispatch where latency matters and queue contention with batch work would block forward progress.
1678
+
1679
+ When in doubt: small model if the call is asynchronous from a user/agent's perspective, larger model if a downstream op depends on the response.
1680
+
1681
+ ### Contention property
1682
+
1683
+ Any skill that calls `$ llm` shares the underlying local-model service with every other process on the deployment that dispatches to the same model. Most local-model services serialize per-model dispatch. A skill that fires asynchronous batch work via `$` (e.g. invoking a batch-classification tool that dispatches N calls to model X) and then immediately calls `$ llm model="X"` will race itself — the synchronous call queues behind the dispatched batch.
1684
+
1685
+ The runtime does not promise concurrency-safe model dispatch. Skill authors and operators own model-tier allocation. The canonical mitigation: use distinct models for the synchronous and asynchronous paths (a smaller model for interactive verdicts, a larger model for batch).
1686
+
1687
+ ### Adopter deployments
1688
+
1689
+ Adopters override the bundled set via `connectors.json`:
1690
+
1691
+ ```jsonc
1692
+ {
1693
+ "localModels": {
1694
+ "default": { "type": "OllamaLocalModel", "model": "llama3.2:3b" },
1695
+ "fast": { "type": "OllamaLocalModel", "model": "phi3:mini" }
1696
+ }
1697
+ }
1698
+ ```
1699
+
1700
+ Adopters with no local models register no LocalModel instances. Skills with `$ llm` ops fail at dispatch with `LocalModel '<name>' not registered`. A `# Requires:` capability declaration promotes this to a compile-time fail-fast — a skill that requires LocalModel won't compile if none is configured. Substrate-blind skills (no `$ llm` ops) work unchanged.
1701
+
1702
+ ## Configuration: substrate selection vs operator config
1703
+
1704
+ Two distinct concerns, often conflated. Keep them separate.
1705
+
1706
+ **Substrate-class selection** — *which* connector implementation fills each slot. Configured via `connectors.json` `substrate.<slot>` entries, or programmatically via `bootstrap()` options. This is what makes a skill portable: the slot is named in config, never in the skill source. Connectors are runtime-resolved — the compiler stays pure read+transform, and compiled artifacts are generic, so any runtime can dispatch them through whatever substrates it has wired.
1707
+
1708
+ **Operator-runtime config** — the per-knob runtime settings: `SKILLSCRIPT_*` env vars, `skillscript.config.json`, and `bootstrap()` options. These tune runtime behavior; they do not select substrate classes.
1709
+
1710
+ Precedence (first wins): explicit `bootstrap()` option > env var > config file > bundled default.
1711
+
1712
+ Per-deployment naming lives in config, not the contract. A given deployment registers concrete instances under whatever names make sense locally; skill authors reference those names.
1713
+
1714
+ The `connectors.json` loader handles literal + `${ENV_VAR}` credential resolution, file-discovery via `$SKILLSCRIPT_HOME`, and a class registry recognizing the bundled connector classes (including `CallbackMcpConnector` and `RemoteMcpConnector`). The per-connector `allowed_tools` field constrains dispatch surface for safe defaults — a cold-author skill against the connector can only invoke allowlisted tools. See ERD §3 + §4 for the full credential-discipline contract.
1715
+
1716
+ ## Per-call identity overrides (McpConnector)
1717
+
1718
+ A skill running under one identity can dispatch against a personal MCP server under a different identity without needing connector-internal state. The merge order at dispatch (top wins):
1719
+
1720
+ 1. **Registry-configured per-connector identity** — set in `connectors.json` (`identity: { agentId: "<id>", isAdmin: false }`) at connector instantiation. Locks an identity to a connector.
1721
+ 2. **Per-call `ctxOverrides`** — threaded by the runtime per the security boundary contract. A skill running as agent X passes `{ agentId: "X", isAdmin: false }` into every `$` op.
1722
+ 3. **(no intrinsic identity)** — adapter forwards whatever the merge produces.
1723
+
1724
+ Configured identity is a *partial merge* — unmentioned keys (e.g., `isAdmin`) flow through from the per-call ctx. Lets a connector lock `agentId` without clobbering the runtime's admin-drop discipline. Default connectors should configure no intrinsic identity, so `ctxOverrides` always wins — preserving the runtime's authority-flow guarantees intact.
1725
+
1726
+ ## Portable shapes
1727
+
1728
+ ```typescript
1729
+ interface PortableMemory {
1730
+ // Core fields — mandatory on every connector return.
1731
+ id: string;
1732
+ summary: string;
1733
+ detail?: string;
1734
+ score?: number;
1735
+
1736
+ // Curated substrate subset — concept-portable, value-substrate-specific.
1737
+ // Top-level access via $(MEMORY.field). Connectors populate when the
1738
+ // concept applies. MUST NOT also be duplicated into metadata.
1739
+ thread_status?: string;
1740
+ pinned?: boolean;
1741
+ confidence?: number;
1742
+ domain_tags?: string[];
1743
+ payload_type?: string;
1744
+ knowledge_type?: string;
1745
+ recipients?: string[];
1746
+ expires_at?: number;
1747
+ created_at?: number;
1748
+ agent_id?: string;
1749
+ vault?: string;
1750
+
1751
+ // Substrate-specific bag. Accessed via $(MEMORY.metadata.X).
1752
+ metadata?: Record<string, unknown>;
1753
+ }
1754
+
1755
+ interface QueryFilters {
1756
+ query: string;
1757
+ limit: number;
1758
+ mode: "fts" | "semantic" | "rerank" | string;
1759
+ [key: string]: unknown;
1760
+ }
1761
+
1762
+ interface McpDispatchCtx {
1763
+ agentId?: string;
1764
+ isAdmin?: boolean;
1765
+ }
1766
+ ```
1767
+
1768
+ ## Field access semantics
1769
+
1770
+ `$(MEMORY.field)` resolves in tiers:
1771
+ 1. Core fields (id, summary, detail, score)
1772
+ 2. Curated substrate subset (thread_status, pinned, etc.)
1773
+ 3. `metadata.X` for everything else
1774
+ 4. Ambient passthrough as literal `$(MEMORY.field)` if unresolved
1775
+
1776
+ **Connector duplication is a contract violation.** If a field is in the curated subset, the connector populates it at top-level only — `metadata.<same_name>` MUST be absent. Otherwise `$(M.thread_status)` and `$(M.metadata.thread_status)` can return different values (silent data divergence). Connectors enforce.
1777
+
1778
+ ## Why connector abstraction matters
1779
+
1780
+ Hard-coupling skills to specific substrates would make information-flow decisions infrastructural rather than skill-authored, defeating the point of skills as the agent's programming language. The connector layer is what lets the same skill body run against substrate A today and run against substrate B tomorrow without rewriting.
1781
+
1498
1782
  ## Lifecycle and status — # Status: header, six canonical states, compile + runtime enforcement
1499
1783
 
1500
1784
  Skillscripts carry an explicit lifecycle state via the `# Status:` header. The compiler and runtime enforce status — a Disabled skillscript cannot fire under any path, regardless of who invokes it.
@@ -2242,5 +2526,5 @@ Hung dispatches hang the skill without explicit timeout configuration. Lean: ski
2242
2526
 
2243
2527
  ---
2244
2528
 
2245
- *Rendered from `skillscript/skillscript-language-reference` — 2026-06-15 12:22 EDT*
2246
- *Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
2529
+ *Rendered from `skillscript/skillscript-language-reference` — 2026-06-15 18:44 EDT*
2530
+ *Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
@@ -2,7 +2,7 @@
2
2
  "provenance_version": "1.0",
3
3
  "language_version": "1.0",
4
4
  "compiler_version": "0.1.0-dev",
5
- "compiled_at": "2026-06-15T22:40:46.115Z",
5
+ "compiled_at": "2026-06-15T23:23:33.053Z",
6
6
  "source_skill": {
7
7
  "name": "hello-world"
8
8
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscript-runtime",
3
- "version": "0.19.13",
3
+ "version": "0.19.14",
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>",