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.
- package/README.md +6 -4
- package/dist/dashboard/spa/app.js +9 -1
- package/dist/mcp-server.d.ts +3 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +24 -2
- package/dist/mcp-server.js.map +1 -1
- package/docs/ERD.md +1139 -0
- package/docs/adopter-playbook.md +240 -0
- package/docs/configuration.md +296 -0
- package/docs/connector-contract-reference.md +164 -0
- package/docs/language-reference.md +1972 -0
- package/docs/sqlite-skill-store.md +167 -0
- package/examples/skillscripts/hello.skill.provenance.json +1 -1
- package/package.json +4 -3
- package/ARCHITECTURE.md +0 -102
|
@@ -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`).
|