skillscript-runtime 0.13.2 → 0.13.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.
@@ -0,0 +1,240 @@
1
+ # Adopter playbook
2
+
3
+ How to wire skillscript-runtime into your deployment. Written for Joe-Programmer: you have your own substrate stack (memory system, agent harness, LLM endpoint, filesystem), and you want skillscript to slot in rather than dictate.
4
+
5
+ This playbook covers the load-bearing decisions, the two wiring patterns, and the conventions that keep your local modifications upstream-merge-friendly.
6
+
7
+ ## The four substrates skillscript expects
8
+
9
+ Skillscript-runtime is substrate-neutral and assumes you have (or will choose):
10
+
11
+ 1. **A filesystem** — for skill source files (`.skill.md`), trace records, possibly a memory database. Sandbox via container, chroot, or limited-privilege process — operator's call.
12
+ 2. **A memory system** — for knowledge retrieval and memory writes. Could be SQLite-FTS (bundled), a vector database, an in-house store, an Obsidian-style notes system — whatever you already have.
13
+ 3. **An LLM endpoint** — Ollama running locally (bundled), a hosted API like OpenAI / Anthropic / Azure, or your own inference server.
14
+ 4. **An agent harness** — where skill output is delivered. Could be tmux sessions, a webhook receiver, an in-house agent runtime, or no harness at all (skills run for their text output only).
15
+
16
+ Each of these maps to a typed connector contract: `SkillStore`, `MemoryStore`, `LocalModel`, `AgentConnector`. Plus `McpConnector` for any external tool you want to invoke from a skill body.
17
+
18
+ ## Case 1 vs Case 2 — the load-bearing wiring decision
19
+
20
+ This is the most important architectural choice you'll make.
21
+
22
+ ### Case 1 — typed-contract wiring (substrate-portable)
23
+
24
+ You implement the typed connector contracts (`MemoryStore`, `LocalModel`, etc.) against your substrate. The bridge classes (`MemoryStoreMcpConnector`, `LocalModelMcpConnector`) surface them as canonical `$ memory` / `$ llm` dispatch.
25
+
26
+ ```typescript
27
+ class MyMemoryStore implements MemoryStore {
28
+ async query(filters: QueryFilters): Promise<PortableMemory[]> { /* ... */ }
29
+ async manifest(): Promise<ManifestInfo> { /* ... */ }
30
+ }
31
+
32
+ registry.registerMemoryStore("primary", new MyMemoryStore());
33
+ registry.registerMcpConnector("memory", new MemoryStoreMcpConnector(new MyMemoryStore()));
34
+ ```
35
+
36
+ **In skills:**
37
+ ```
38
+ $ memory mode=fts query="customer feedback" limit=10 -> CONTEXT
39
+ ```
40
+
41
+ This same skill body runs unchanged against your substrate, against SQLite-FTS (bundled), against Pinecone, against any substrate that conforms to the typed contract. **Skills are portable.**
42
+
43
+ ### Case 2 — MCP-tools wiring (substrate-locked)
44
+
45
+ Your substrate exposes itself as MCP tools (via a local MCP server or remote one). You wire it as an `McpConnector` (typically `RemoteMcpConnector` for spawned MCP processes) and skills reference its tools by name with substrate-specific kwargs.
46
+
47
+ ```typescript
48
+ // connectors.json:
49
+ {
50
+ "my_memory": {
51
+ "class": "RemoteMcpConnector",
52
+ "config": {
53
+ "command": "my-memory-mcp-server",
54
+ "args": ["--db", "/var/memory"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ **In skills:**
61
+ ```
62
+ $ my_memory.search query="customer feedback" vault="team" tags=["urgent"] -> CONTEXT
63
+ ```
64
+
65
+ This skill body is locked to `my_memory` — its specific kwargs (`vault`, `tags`) and response shape. To move to a different substrate, every call site has to be rewritten.
66
+
67
+ ### Picking — the tradeoff
68
+
69
+ | Aspect | Case 1 (typed) | Case 2 (MCP) |
70
+ |---|---|---|
71
+ | Skill portability | ✓ portable | ✗ substrate-locked |
72
+ | Substrate feature coverage | Limited to typed contract | Full substrate surface |
73
+ | Implementation effort | Implement typed interface | Wire existing MCP server |
74
+ | Best for | Skills you want to ship | Substrate-specific power features |
75
+
76
+ **The choice is per-skill, not per-substrate.** You can wire both — register `memory` (typed-contract via bridge) AND `my_memory` (MCP) — and let skills opt into portability by which connector name they reference.
77
+
78
+ For the substrate-portability claim to hold, **the substrates you care about must be Case-1-wired**.
79
+
80
+ ## Joe Programmer setup walkthrough
81
+
82
+ ### 1. Install + initialize
83
+
84
+ ```bash
85
+ npm install -g skillscript-runtime
86
+ skillfile init --here
87
+ ```
88
+
89
+ This creates `~/.skillscript/` with `skills/`, `traces/`, an empty `connectors.json`, and a `config.toml` stub.
90
+
91
+ ### 2. Decide on substrate wiring
92
+
93
+ For each of the four substrates (memory, LLM, agent harness, MCP tools), decide Case 1 or Case 2. The onboarding scaffold (`examples/onboarding-scaffold/`) is Case 1 end-to-end against file-backed memory + OpenAI + tmux.
94
+
95
+ ### 3. Configure runtime knobs
96
+
97
+ Create `skillscript.config.json` in your `$SKILLSCRIPT_HOME`:
98
+
99
+ ```json
100
+ {
101
+ "skillsDir": "${SKILLSCRIPT_HOME}/skills",
102
+ "traceDir": "${SKILLSCRIPT_HOME}/traces",
103
+ "memoryDbPath": "${SKILLSCRIPT_HOME}/memory.json",
104
+ "dashboard": { "port": 7878 }
105
+ }
106
+ ```
107
+
108
+ `${VAR}` substitutes against `process.env`. See `skillscript.config.json.example` in the repo for the full surface.
109
+
110
+ ### 4. Wire your substrates
111
+
112
+ **For the bundled CLI path** (no custom code): use `connectors.json` to declare your MCP servers; use `OPENAI_API_KEY` / `OLLAMA_BASE_URL` env vars; run `skillfile dashboard --config ./skillscript.config.json`.
113
+
114
+ **For custom substrates**: write your own bootstrap. See `examples/custom-bootstrap.example.ts` and `examples/onboarding-scaffold/bootstrap.ts` for complete worked walkthroughs.
115
+
116
+ If you have a custom JSON-instantiable `McpConnector` class, register it with `registerConnectorClass` before loading config:
117
+
118
+ ```typescript
119
+ import { registerConnectorClass, loadConnectorsConfig } from "skillscript-runtime";
120
+ import { MyAdopterConnector } from "./my-adopter-connector.js";
121
+
122
+ registerConnectorClass("MyAdopterConnector", {
123
+ ctor: MyAdopterConnector,
124
+ fromConfig: (cfg) => new MyAdopterConnector(cfg),
125
+ });
126
+
127
+ const { connectors } = loadConnectorsConfig({ path: "./connectors.json" });
128
+ ```
129
+
130
+ ### 5. Two-instance posture
131
+
132
+ Running dev-skillscript alongside an adopter-wiring instance on the same machine:
133
+
134
+ ```bash
135
+ # dev
136
+ skillfile dashboard
137
+
138
+ # adopter (different port + paths)
139
+ SKILLSCRIPT_HOME=/path/to/adopter skillfile dashboard --config /path/to/adopter/skillscript.config.json
140
+ ```
141
+
142
+ Each instance reads its own config; ports/paths/db files don't collide.
143
+
144
+ ## Conventions for upstream-merge-friendly modifications
145
+
146
+ If your wiring needs require modifying skillscript-runtime source (rather than just configuration), follow these conventions to minimize merge friction:
147
+
148
+ ### 1. Prefer dedicated adopter files over editing upstream
149
+
150
+ Put your code in dedicated paths upstream won't touch:
151
+
152
+ ```
153
+ src/connectors/local/my-memory-adapter.ts ← adopter-owned
154
+ src/connectors/local/my-llm-adapter.ts ← adopter-owned
155
+ ```
156
+
157
+ Upstream changes to `src/connectors/memory-store.ts` won't conflict with your `local/` files.
158
+
159
+ ### 2. Use the public registration API; don't edit the closed-set Map
160
+
161
+ `KNOWN_CONNECTOR_CLASSES` in `src/connectors/config.ts` is upstream-owned. Add your classes via `registerConnectorClass(name, entry)` from your bootstrap instead. Closes the merge-conflict bait of editing that file every release.
162
+
163
+ ### 3. Mark unavoidable upstream-file edits with sentinels
164
+
165
+ When you genuinely have to edit an upstream file, mark the change:
166
+
167
+ ```typescript
168
+ // ADOPTER:myorg — extend dispatch to call our auditor before forward
169
+ if (process.env["MYORG_AUDIT"] === "1") { /* ... */ }
170
+ ```
171
+
172
+ The `// ADOPTER:myorg —` prefix is greppable across merges; your future-self can re-evaluate whether the modification is still needed when upstream changes the surrounding code.
173
+
174
+ ### 4. Treat `src/bootstrap.ts` as reference, not canonical
175
+
176
+ The bundled `bootstrap()` is a starting point. For deployments with custom substrates, write your own bootstrap that imports the public APIs (`Registry`, the connector classes, `loadConnectorsConfig`, `loadSkillscriptConfig`, etc.). Modifying the bundled bootstrap creates churn on every upstream release.
177
+
178
+ See `examples/custom-bootstrap.example.ts` for a worked walkthrough.
179
+
180
+ ## Substrate ship-status
181
+
182
+ | Substrate | Shipped contract | Shipped impls | Shipped bridge |
183
+ |---|---|---|---|
184
+ | SkillStore | ✓ `load`/`query`/`store`/`update_status`/`delete`/`versions`/`metadata`/`staticCapabilities` | `FilesystemSkillStore`, `SqliteSkillStore` | n/a |
185
+ | MemoryStore | ✓ `query`/`write` | `SqliteMemoryStore` | ✓ `MemoryStoreMcpConnector` |
186
+ | LocalModel | ✓ `run` | `OllamaLocalModel` | ✓ `LocalModelMcpConnector` |
187
+ | McpConnector | ✓ `call` | `RemoteMcpConnector`, `CallbackMcpConnector` | n/a |
188
+ | AgentConnector | ✓ `list_agents`/`deliver`/`wake`/`health_check`/`request_response`; optional `agent_status` | `NoOpAgentConnector` (default), `HttpWebhookAgentConnector` | n/a |
189
+
190
+ **Notable gaps the playbook should be honest about:**
191
+
192
+ - **4 of 6 trigger sources parse but don't fire.** `cron` and `session: start` work; `event`, `agent-event`, `file-watch`, `sensor` are parser-only stubs awaiting the event-bus surface.
193
+ - **Output kinds are intentionally substrate-neutral.** `# Output:` accepts `text` / `agent: <name>` / `template: <name>` / `file: <path>` / `none`. Substrate-specific values (`slack:`, `card:`, etc.) are deliberately 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.
194
+ - **Authorization is hash-token approval.** Skills must carry `# Status: Approved vN:<token>` where the token re-computes from the body minus its `# Status:` line. Bundled `v1:` is CRC32 — discipline-barrier strength, suited to single-operator deployments. Adversarial threat models swap a stronger function:
195
+
196
+ ```ts
197
+ import { registerApprovalFn, setPreferredApprovalVersion } from "skillscript-runtime";
198
+ import { createHmac } from "node:crypto";
199
+
200
+ // v2: HMAC-SHA256 with operator-held key. Agent that knows the algorithm
201
+ // can no longer self-stamp without the key.
202
+ const key = process.env["APPROVAL_HMAC_KEY"]!;
203
+ registerApprovalFn("v2", (body) => createHmac("sha256", key).update(body).digest("hex"));
204
+ setPreferredApprovalVersion("v2"); // dashboard now stamps v2 on Approve clicks
205
+ ```
206
+
207
+ Wire this in your bootstrap BEFORE any skill is stamped — otherwise existing skills carry `v1:` tokens that still verify (CRC32 stays registered) but new approvals use the upgraded function. The runtime maintains a per-version registry, so mixed-version skill bodies coexist cleanly.
208
+
209
+ ## Skill discovery + cross-agent composition
210
+
211
+ Under Case-1 wiring against a memory substrate that holds skill payloads (e.g., AMP's payload_type model where compiled `.skill.md` artifacts live alongside thread / prose / document entries), skill discovery uses the canonical `$ memory` surface:
212
+
213
+ ```
214
+ $ memory mode=fts query="incident triage" limit=5 -> SKILLS
215
+ foreach S in ${SKILLS.items}:
216
+ execute_skill(skill_name="${S.name}", ...) -> RESULT
217
+ ```
218
+
219
+ This works *only* when the memory substrate is Case-1 wired (typed-contract via bridge). Under Case-2 wiring, you'd need substrate-specific tool calls (`$ amp.search query=... payload_type=skill`) which are non-portable.
220
+
221
+ ## Contributing — dispatch-shape discipline
222
+
223
+ The multi-layer-promise pattern (lint passes; runtime fails, or vice versa) is the recurring failure mode for dispatch-shape work. `validateQualifiedDispatch` is the shared validator lint and runtime both call. To prevent the next recurrence, every PR that introduces a new dispatch shape (a new way of writing `$ ...` ops, a new connector class entry point, a new lifecycle hook on `# Output:`) must land with:
224
+
225
+ 1. **Lint test** — fixture that exercises the shape with lint only (`lint(source, {registry})`)
226
+ 2. **Runtime test** — same shape executed end-to-end (`executeSkillByName` or `executeSkillFromSource`)
227
+ 3. **E2E test** — the full user path (write skill → store → execute via MCP, or trigger fire → dispatch)
228
+
229
+ 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 — file a thread before merging.
230
+
231
+ 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.
232
+
233
+ ## Resources
234
+
235
+ - **Onboarding scaffold** — `examples/onboarding-scaffold/` — complete adopter deployment with file-backed memory + OpenAI + tmux
236
+ - **Custom bootstrap walkthrough** — `examples/custom-bootstrap.example.ts` — registering custom MCP connector classes
237
+ - **Connectors example** — `scaffold/connectors.json` — annotated `connectors.json` shape
238
+ - **Language reference** — `docs/language-reference.md` — skill syntax + frontmatter + lint codes
239
+ - **Connector contracts** — `docs/connector-contract-reference.md` — substrate-neutral contract surfaces
240
+ - **Configuration** — `docs/configuration.md` — `connectors.json` shape + substrate selection
@@ -0,0 +1,296 @@
1
+ # Configuration
2
+
3
+ How to configure a skillscript-runtime deployment.
4
+
5
+ The single config file is **`~/.skillscript/connectors.json`** (or any path passed via `--connectors`). It has two top-level concerns:
6
+
7
+ 1. **`substrate`** — which `SkillStore`, `MemoryStore`, and `LocalModel` the runtime hosts (MCP server + web dashboard) use.
8
+ 2. **Named MCP connector instances** — `youtrack`, `github`, etc. — invoked via `$ <name>` in skill source.
9
+
10
+ The runtime loads `connectors.json` at startup. Missing file → graceful empty config (substrate defaults to filesystem skills + conditional sqlite memories; no MCP connectors). Malformed JSON or unknown fields → structured errors surfaced at bootstrap.
11
+
12
+ ---
13
+
14
+ ## Quick start
15
+
16
+ A typical out-of-the-box `~/.skillscript/connectors.json`:
17
+
18
+ ```json
19
+ {
20
+ "substrate": {
21
+ "skill_store": "filesystem",
22
+ "memory_store": "sqlite",
23
+ "local_model": null
24
+ }
25
+ }
26
+ ```
27
+
28
+ Equivalent to omitting the file entirely — these are the base config defaults.
29
+
30
+ To switch skills storage to SQLite:
31
+
32
+ ```json
33
+ {
34
+ "substrate": {
35
+ "skill_store": "sqlite"
36
+ }
37
+ }
38
+ ```
39
+
40
+ Restart `skillfile dashboard` (or `skillfile serve`). The MCP server + dashboard UI now read/write skills from `~/.skillscript/skills/skills.db` instead of `.skill.md` files.
41
+
42
+ > **Heads up on startup logs.** Sqlite-backed substrates use the built-in `node:sqlite` module, which is still flagged experimental in Node 22. Expect this line on every launch until Node de-experimentalizes it: `ExperimentalWarning: SQLite is an experimental feature and might change at any time`. Harmless; can be silenced per-process with `NODE_OPTIONS="--disable-warning=ExperimentalWarning"` if it clutters your logs.
43
+
44
+ ---
45
+
46
+ ## The substrate section
47
+
48
+ Singleton substrate connectors. Each slot accepts one of four shapes:
49
+
50
+ ### Short form — bare string
51
+
52
+ ```json
53
+ "skill_store": "sqlite"
54
+ ```
55
+
56
+ Wires the bundled implementation for that type with default config (e.g., dbPath under `~/.skillscript/`).
57
+
58
+ Valid short-form values per slot:
59
+
60
+ | Slot | Values |
61
+ |---|---|
62
+ | `skill_store` | `"filesystem"` \| `"sqlite"` |
63
+ | `memory_store` | `"sqlite"` |
64
+ | `local_model` | (none — `"ollama"` requires the object form with `defaultModelTag`; see below) |
65
+
66
+ ### Null — explicit "no substrate"
67
+
68
+ ```json
69
+ "local_model": null
70
+ ```
71
+
72
+ The runtime doesn't register a connector for this slot. Useful for explicitly disabling LocalModel when nothing local is available.
73
+
74
+ ### Object form — override defaults
75
+
76
+ ```json
77
+ "skill_store": {
78
+ "type": "sqlite",
79
+ "config": {
80
+ "dbPath": "/var/skillscript/skills.db"
81
+ }
82
+ }
83
+ ```
84
+
85
+ `type` picks the bundled impl; `config` is passed to its constructor. Per-type config fields:
86
+
87
+ | Type | Config fields |
88
+ |---|---|
89
+ | `filesystem` (skill_store) | none — uses the CLI's `skillsDir` |
90
+ | `sqlite` (skill_store) | `dbPath` (default: `<skillsDir>/skills.db`) |
91
+ | `sqlite` (memory_store) | `dbPath` (default: CLI's `MEMORY_DB` or `<skillsDir>/memories.db`) |
92
+ | `ollama` (local_model) | `baseUrl` (default: `OLLAMA_BASE_URL` env or `http://localhost:11434`), **`defaultModelTag` (required — e.g., `"gemma2:9b"`, `"llama3.1:8b"`)** |
93
+
94
+ Worked Ollama example (because the short form isn't valid for `local_model`):
95
+
96
+ ```json
97
+ {
98
+ "substrate": {
99
+ "local_model": {
100
+ "type": "ollama",
101
+ "config": {
102
+ "defaultModelTag": "gemma2:9b"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ Pin the model tag explicitly — must be a tag your Ollama instance has pulled (`ollama pull gemma2:9b`). Bare `"local_model": "ollama"` errors out at bootstrap because the model name is too important to silently default.
110
+
111
+ ### Custom form — adopter-written impl
112
+
113
+ ```json
114
+ "skill_store": {
115
+ "type": "custom",
116
+ "module": "./my-amp-skill-store.js",
117
+ "export": "AmpSkillStore",
118
+ "config": {
119
+ "vault": "team"
120
+ }
121
+ }
122
+ ```
123
+
124
+ References an adopter-written class implementing the relevant contract. `module` is the path to the JS file; `export` is the named export (defaults to `default`); `config` is passed to the constructor.
125
+
126
+ > **Limitation**: sync `bootstrap()` can't dynamic-import. Custom-via-connectors.json surfaces a clear error and falls back to the default. Adopters wanting custom impls today write a programmatic bootstrap that calls `registry.registerSkillStore("primary", new AmpSkillStore(...))` directly — same pattern as the runtime's reference `bootstrap()`. Async-bootstrap with dynamic-import support is planned.
127
+
128
+ ---
129
+
130
+ ## Precedence
131
+
132
+ When multiple config sources speak:
133
+
134
+ 1. **Programmatic opts** (`opts.skillStore` passed to `bootstrap()`) — explicit, highest priority
135
+ 2. **`connectors.json` substrate section** — declarative, deployment-durable
136
+ 3. **Built-in default** — fallback (filesystem skill_store; conditional sqlite memory_store; no local_model)
137
+
138
+ If two configs disagree, the higher-priority one wins; lower-priority is ignored without error.
139
+
140
+ ---
141
+
142
+ ## Which surfaces honor substrate config?
143
+
144
+ | Surface | Honors substrate? | Reasoning |
145
+ |---|---|---|
146
+ | **MCP server** (`skillfile serve`, dashboard `/rpc`) | ✓ | MCP is the agent-facing surface; must read/write whichever store the deployment chose |
147
+ | **Web dashboard** (`skillfile dashboard`) | ✓ | Same as MCP — agents and humans connect to the same runtime |
148
+ | **Programmatic embed** (your own bootstrap) | ✓ | You pass `opts.skillStore` directly; the runtime takes whatever |
149
+ | `skillfile compile` | ✗ filesystem-only | Authoring loop: `vim foo.skill.md && skillfile compile foo`. Only coherent against FS. |
150
+ | `skillfile lint` | ✗ filesystem-only | Same as compile. |
151
+ | `skillfile audit` | ✗ filesystem-only | Operates on a provenance file + the FS-authored source. |
152
+ | `skillfile list` | ✗ filesystem-only | Filesystem listing of `.skill.md` files. |
153
+
154
+ The four authoring CLI commands stay FS-pinned by design — they're the filesystem-first authoring loop. Sqlite-backed skills are authored via the dashboard UI or the `skill_write` MCP tool, not these CLI commands.
155
+
156
+ ---
157
+
158
+ ## Named MCP connector instances
159
+
160
+ Per-host MCP connector wiring. Each top-level key (other than `substrate`) defines a named connector referenced via `$ <name>` in skill source.
161
+
162
+ ```json
163
+ {
164
+ "substrate": { "skill_store": "sqlite" },
165
+
166
+ "youtrack": {
167
+ "class": "RemoteMcpConnector",
168
+ "config": {
169
+ "command": "npx",
170
+ "args": ["mcp-remote", "https://example.youtrack.cloud/mcp"],
171
+ "env": { "AUTH_HEADER": "Bearer ${YOUTRACK_TOKEN}" }
172
+ }
173
+ },
174
+
175
+ "github": {
176
+ "class": "RemoteMcpConnector",
177
+ "config": { /* ... */ },
178
+ "allowed_tools": ["search_repos", "get_issue"]
179
+ }
180
+ }
181
+ ```
182
+
183
+ Each entry needs:
184
+
185
+ - **`class`** — a class from the closed-set registry. Today: `RemoteMcpConnector` (stdio-bridged remote MCP). Adopters can register custom classes via `registerConnectorClass()` from their bootstrap.
186
+ - **`config`** — passed to the class's `fromConfig()` factory. Schema is class-specific.
187
+ - **`allowed_tools`** (optional) — per-connector tool allowlist. `undefined` = allow all; `[]` = allow none; listed array = exactly those.
188
+
189
+ ### Credential discipline
190
+
191
+ `connectors.json` is secret-bearing. The repo `.gitignore` excludes it by default; `connectors.json.example` (not real values) is committed as a template. For deployments, prefer `${VAR}` env-var substitution over literals — commit the `${...}` references; keep secrets in deployment environment.
192
+
193
+ Skillscript warns at bootstrap if `connectors.json` lives in a git-tracked directory without a `.gitignore` entry.
194
+
195
+ ### `${VAR}` substitution
196
+
197
+ ```json
198
+ "env": { "AUTH_HEADER": "Bearer ${YOUTRACK_TOKEN}" }
199
+ ```
200
+
201
+ `${NAME}` resolves from `process.env` at load time. Missing env var → clear startup error (not silent empty string).
202
+
203
+ The `config.env` block is itself resolved first, then merged into the substitution scope for the rest of the config — letting you compose values:
204
+
205
+ ```json
206
+ "config": {
207
+ "env": { "AUTH_HEADER": "Bearer ${YOUTRACK_TOKEN}" },
208
+ "args": ["--header", "Authorization:${AUTH_HEADER}"]
209
+ }
210
+ ```
211
+
212
+ This matches the Claude Desktop `mcp.json` convention.
213
+
214
+ ### Inline comments
215
+
216
+ Underscore-prefixed top-level keys (`_comment`, `_note_security`, etc.) are ignored by the parser. Use them inline to document your config without external comments — the JSON spec doesn't natively support comments, so the runtime treats `_*` keys as the convention.
217
+
218
+ ```json
219
+ {
220
+ "_comment": "Last edited 2026-05-28 — switched skill_store to sqlite for AMP-style dogfooding",
221
+ "substrate": { "skill_store": "sqlite" }
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Adopter-custom substrate impls
228
+
229
+ Write `class FooSkillStore implements SkillStore { ... }` (or MemoryStore, LocalModel). Wire it via either:
230
+
231
+ **(a) Programmatic bootstrap (recommended today)** — write your own bootstrap script that constructs the registry directly:
232
+
233
+ ```typescript
234
+ import { Registry, McpServer, Scheduler } from "skillscript-runtime";
235
+ import { FooSkillStore } from "./foo-skill-store.js";
236
+
237
+ const registry = new Registry();
238
+ registry.registerSkillStore("primary", new FooSkillStore({ /* config */ }));
239
+ // ... register other substrates, then construct Scheduler + McpServer + DashboardServer
240
+ ```
241
+
242
+ See [`docs/adopter-playbook.md`](adopter-playbook.md) for the full pattern.
243
+
244
+ **(b) `connectors.json` custom form** (deferred to follow-up):
245
+
246
+ ```json
247
+ "skill_store": {
248
+ "type": "custom",
249
+ "module": "./foo-skill-store.js",
250
+ "export": "FooSkillStore",
251
+ "config": { ... }
252
+ }
253
+ ```
254
+
255
+ Currently surfaces an error and falls back to the default — sync `bootstrap()` can't dynamic-import. Track the async-bootstrap promotion as future work.
256
+
257
+ ---
258
+
259
+ ## Operational tips
260
+
261
+ ### Switching substrates without losing data
262
+
263
+ The substrate switch is a runtime wiring change, not a data migration. Switching from `filesystem` to `sqlite` doesn't move your `.skill.md` files into the Sqlite db automatically — the dashboard will show an empty skill list because the new Sqlite db is fresh.
264
+
265
+ To preserve your skills across a switch:
266
+
267
+ 1. Read each `.skill.md` file from `~/.skillscript/skills/` (or your `skillsDir`)
268
+ 2. Call `skill_write` MCP tool (or `store.store(name, source)` programmatically) to land them in the new substrate
269
+
270
+ A bundled migration tool isn't shipped — different adopters want different things (rename normalization, metadata enrichment, dry-run safety).
271
+
272
+ ### Multi-instance posture
273
+
274
+ Running both a dev instance (filesystem) and an adopter instance (sqlite or custom) side by side is common. Use separate `--port` + `--connectors` paths:
275
+
276
+ ```bash
277
+ # Dev — filesystem skills, port 7878
278
+ skillfile dashboard --host 127.0.0.1 --port 7878
279
+
280
+ # Adopter — sqlite skills, port 7879
281
+ skillfile dashboard --host 127.0.0.1 --port 7879 --connectors ~/.skillscript/adopter-connectors.json
282
+ ```
283
+
284
+ See [`docs/adopter-playbook.md`](adopter-playbook.md) § "Two-instance posture" for the broader pattern.
285
+
286
+ ### Verifying which substrate is wired
287
+
288
+ After a config change + restart, verify via `runtime_capabilities`:
289
+
290
+ ```bash
291
+ curl -s -X POST http://localhost:7878/rpc \
292
+ -H "content-type: application/json" \
293
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"runtime_capabilities","arguments":{"include":["skillStores"]}}}' | jq
294
+ ```
295
+
296
+ Output includes the wired SkillStore's `implementation` field (`FilesystemSkillStore` or `SqliteSkillStore`).