skillscript-runtime 0.7.0 → 0.7.2
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 +74 -8
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +15 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +14 -1
- package/dist/compile.js.map +1 -1
- package/dist/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +10 -0
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/local-model-mcp.d.ts +9 -0
- package/dist/connectors/local-model-mcp.d.ts.map +1 -0
- package/dist/connectors/local-model-mcp.js +73 -0
- package/dist/connectors/local-model-mcp.js.map +1 -0
- package/dist/connectors/memory-store-mcp.d.ts +9 -0
- package/dist/connectors/memory-store-mcp.d.ts.map +1 -0
- package/dist/connectors/memory-store-mcp.js +94 -0
- package/dist/connectors/memory-store-mcp.js.map +1 -0
- package/dist/help-content.d.ts.map +1 -1
- package/dist/help-content.js +316 -238
- package/dist/help-content.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +282 -44
- package/dist/lint.js.map +1 -1
- package/dist/parser.d.ts +22 -2
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +119 -7
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -1
- package/dist/runtime.js.map +1 -1
- package/examples/hello.skill.provenance.json +1 -1
- package/package.json +1 -1
- package/scaffold/connectors.json +15 -13
package/dist/help-content.js
CHANGED
|
@@ -13,14 +13,40 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Token estimates per topic are approximate; help() output is intended
|
|
15
15
|
// for an agent's working context, not for human reading.
|
|
16
|
-
const QUICKSTART = `# Skillscript — quickstart
|
|
16
|
+
const QUICKSTART = `# Skillscript — quickstart (v0.7.0+ canonical surface)
|
|
17
17
|
|
|
18
18
|
Skillscript is a declarative language for authoring agent workflows. A
|
|
19
19
|
skill is a small program with named targets composed of typed ops. The
|
|
20
20
|
runtime walks dependencies backward from the goal target (declared via
|
|
21
21
|
\`default:\`) and dispatches each op in topological order.
|
|
22
22
|
|
|
23
|
-
## 1.
|
|
23
|
+
## 1. The skill model — trigger → process → deliver
|
|
24
|
+
|
|
25
|
+
Every skill follows the same shape:
|
|
26
|
+
|
|
27
|
+
1. **Trigger fires** — cron, command, event, session-start, or programmatic invocation
|
|
28
|
+
2. **Process** — pull data (MCP / memory / file), classify / compose via sub-LLM + iteration, build the deliverable
|
|
29
|
+
3. **Deliver** — via one or more of three channels
|
|
30
|
+
|
|
31
|
+
The three delivery channels are all first-class:
|
|
32
|
+
|
|
33
|
+
| Channel | Op | When you'd use it |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| **Embedded prompt** | \`emit(text="...")\` | Skill output is injected into the agent's turn as prompt-context |
|
|
36
|
+
| **Memory handoff** | \`$ memory_write content="..." addressed_to="<agent>" -> R\` | Skill writes a memory the target agent picks up via mailbox |
|
|
37
|
+
| **File handoff** | \`file_write(path="...", content="...")\` | Skill writes a file at a known location for the agent to read |
|
|
38
|
+
|
|
39
|
+
## 2. The three op classes
|
|
40
|
+
|
|
41
|
+
| Class | Shape | Examples |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| **Mutation statements** | \`$verb VAR = value\` / \`$verb VAR <value>\` | \`$set NAME = "Scott"\`, \`$append LIST <item>\` |
|
|
44
|
+
| **Runtime-intrinsic function-calls** | \`verb(kwarg=value, ...) [-> BINDING]\` | \`emit(text="...")\`, \`ask(prompt="...") -> R\`, \`inline(skill="...")\`, \`execute_skill(skill_name="...") -> R\`, \`shell(command="...") -> R\`, \`file_read(path="...") -> R\`, \`file_write(path="...", content="...")\` |
|
|
45
|
+
| **External MCP dispatch** | \`$ <connector> kwarg=value, ... [-> BINDING]\` | \`$ youtrack_search query="..." -> R\`, \`$ llm prompt="..." -> R\`, \`$ memory mode=fts query="..." -> R\` |
|
|
46
|
+
|
|
47
|
+
The \`$\` prefix marks **state-affecting ops** (mutation OR external dispatch). Function-call shape marks **language-intrinsic ops the runtime knows directly**.
|
|
48
|
+
|
|
49
|
+
## 3. Shape of a skill file
|
|
24
50
|
|
|
25
51
|
\`\`\`
|
|
26
52
|
# Skill: my-skill ← required: skill name
|
|
@@ -30,57 +56,46 @@ runtime walks dependencies backward from the goal target (declared via
|
|
|
30
56
|
# Triggers: cron: 0 9 * * * ← optional: autonomous-dispatch sources
|
|
31
57
|
|
|
32
58
|
target_a: ← a named block of ops
|
|
33
|
-
|
|
34
|
-
|
|
59
|
+
$ ticketing_search query="state:open" -> ISSUES
|
|
60
|
+
$ llm prompt="Summarize: \${ISSUES}" -> SUMMARY
|
|
35
61
|
|
|
36
62
|
target_b: target_a ← Make-style: target_b depends on target_a
|
|
37
|
-
|
|
63
|
+
emit(text="\${SUMMARY}")
|
|
38
64
|
|
|
39
65
|
default: target_b ← goal target the runtime walks toward
|
|
40
66
|
\`\`\`
|
|
41
67
|
|
|
42
|
-
##
|
|
68
|
+
## 4. Variable substitution
|
|
43
69
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| \`~ prompt="..." -> VAR\` | LocalModel call; binds output |
|
|
48
|
-
| \`> mode=... query=... limit=N -> VAR\` | Memory retrieval |
|
|
49
|
-
| \`@ command args -> VAR\` | Shell exec (structural sandbox; \`@ unsafe\` for full bash) |
|
|
50
|
-
| \`! text\` | Emit text to the user / output channel |
|
|
51
|
-
| \`?? prompt -> VAR\` | Ask user (interactive mode only) |
|
|
52
|
-
| \`$set NAME=value\` | Explicit variable binding |
|
|
53
|
-
| \`$append VAR <value>\` | Accumulate a value into a list-typed VAR (v0.3.0) |
|
|
54
|
-
| \`& skill-name args -> VAR\` | Inline a data-skill |
|
|
70
|
+
Use \`\${VAR}\` (canonical) inside any kwarg value or emit body. Field access works: \`\${ISSUE.title}\`. Filter chains: \`\${VAR|trim|length}\`. Missing-value fallback: \`\${VAR|fallback:"-"}\`.
|
|
71
|
+
|
|
72
|
+
The legacy \`$(VAR)\` form still compiles during the v0.7.x grace period (tier-2 \`deprecated-substitution-shape\` warning); tier-1 promotion in v0.8/v0.9.
|
|
55
73
|
|
|
56
|
-
##
|
|
74
|
+
## 5. Result binding + fallback
|
|
57
75
|
|
|
58
|
-
Most ops accept \`-> VAR\` to bind their output. Reference later via
|
|
59
|
-
Optional \`(fallback: "default")\` after \`-> VAR\` binds the fallback on dispatch
|
|
60
|
-
error instead of propagating.
|
|
76
|
+
Most dispatch ops accept \`-> VAR\` to bind their output. Reference later via \`\${VAR}\`. Optional \`(fallback: "default")\` after \`-> VAR\` binds the fallback on dispatch error instead of propagating.
|
|
61
77
|
|
|
62
|
-
##
|
|
78
|
+
## 6. Branching
|
|
63
79
|
|
|
64
80
|
\`\`\`
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
elif
|
|
68
|
-
|
|
81
|
+
if \${VERDICT} == "urgent":
|
|
82
|
+
emit(text="sound the alarm")
|
|
83
|
+
elif \${COUNT} > "10":
|
|
84
|
+
emit(text="threshold breached: \${COUNT} items")
|
|
69
85
|
else:
|
|
70
|
-
|
|
86
|
+
emit(text="all clear")
|
|
71
87
|
\`\`\`
|
|
72
88
|
|
|
73
|
-
Numeric comparison (\`<\` / \`>\` / \`<=\` / \`>=\`) coerces both sides via Number();
|
|
74
|
-
non-numeric operands raise TypeMismatchError.
|
|
89
|
+
Numeric comparison (\`<\` / \`>\` / \`<=\` / \`>=\`) coerces both sides via Number(); non-numeric operands raise TypeMismatchError.
|
|
75
90
|
|
|
76
|
-
##
|
|
91
|
+
## 7. Iteration
|
|
77
92
|
|
|
78
93
|
\`\`\`
|
|
79
|
-
foreach M in
|
|
80
|
-
|
|
94
|
+
foreach M in \${MEMORIES}:
|
|
95
|
+
emit(text="Processing \${M.id}: \${M.summary}")
|
|
81
96
|
\`\`\`
|
|
82
97
|
|
|
83
|
-
##
|
|
98
|
+
## 8. How to see what's broken
|
|
84
99
|
|
|
85
100
|
- \`lint_skill({source})\` — diagnostics across tier-1 (errors), tier-2 (warnings), tier-3 (advisories)
|
|
86
101
|
- \`compile_skill({source, inputs?})\` — render the compiled artifact + surface compile errors
|
|
@@ -89,173 +104,201 @@ foreach M in $(MEMORIES):
|
|
|
89
104
|
## Worked end-to-end example
|
|
90
105
|
|
|
91
106
|
\`\`\`
|
|
92
|
-
# Skill: morning-
|
|
93
|
-
# Description: Cron-fired
|
|
107
|
+
# Skill: morning-showstopper-sweep
|
|
108
|
+
# Description: Cron-fired pre-triage; delivers triaged showstoppers to oncall agent via prompt-context channel
|
|
94
109
|
# Status: Approved
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@ curl -s "wttr.in/$(LOCATION|url)?format=j1" -> RAW (fallback: "")
|
|
110
|
+
# Autonomous: true
|
|
111
|
+
# Vars: PROJECT=INFRA
|
|
112
|
+
# Triggers: cron: 0 8 * * MON-FRI
|
|
113
|
+
# Output: prompt-context: oncall
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
run:
|
|
116
|
+
$ ticketing_search query="project:\${PROJECT} severity:showstopper state:Open" limit=20 -> ISSUES
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
emit(text="Morning showstoppers for \${PROJECT} — \${ISSUES.totalCount} open:")
|
|
119
|
+
emit(text="")
|
|
120
|
+
foreach ISSUE in \${ISSUES.items}:
|
|
121
|
+
$ llm prompt="Two-line triage hypothesis for: \${ISSUE.summary}" -> ANALYSIS
|
|
122
|
+
emit(text="## \${ISSUE.id}: \${ISSUE.summary}")
|
|
123
|
+
emit(text="\${ANALYSIS}")
|
|
124
|
+
emit(text="")
|
|
109
125
|
|
|
110
|
-
default:
|
|
126
|
+
default: run
|
|
111
127
|
\`\`\`
|
|
112
128
|
|
|
129
|
+
What this example demonstrates:
|
|
130
|
+
- **Trigger** — cron at 8am weekdays
|
|
131
|
+
- **Process** — \`$ ticketing_search\` MCP dispatch (substrate-portable: adopters wire whatever ticketing connector they have), \`foreach\` iteration with per-item \`$ llm\` sub-classification
|
|
132
|
+
- **Deliver** — \`emit(text=...)\` per line accumulates as prompt-context, routed to the on-call agent via the \`# Output: prompt-context: oncall\` channel declaration
|
|
133
|
+
- **Authorization** — \`# Autonomous: true\` declares this skill cron-fired and unattended; mutation ops within are silenced from the user-confirmation lint
|
|
134
|
+
|
|
135
|
+
**Pattern note:** prefer \`emit(text="...")\` per line over building a multi-line accumulator string with \`$append\`. The runtime threads emissions into prompt-context naturally, and the per-line shape is what cold authors reach for. Multi-line string accumulators are a real pattern for file-writing scenarios; emit is the natural choice for prompt-context delivery.
|
|
136
|
+
|
|
113
137
|
Use \`help({topic: "ops"})\`, \`help({topic: "frontmatter"})\`, \`help({topic: "examples"})\`,
|
|
114
138
|
\`help({topic: "connectors"})\`, or \`help({topic: "lint-codes"})\` for deeper sections.
|
|
139
|
+
|
|
140
|
+
**Note on legacy syntax.** Legacy symbol-form ops (\`~\`, \`>\`, \`@\`, \`!\`, \`??\`, \`&\`) and \`$(VAR)\` substitution continue to compile during the v0.7.x grace period with tier-2 deprecation warnings. CHANGELOG.md \`## 0.7.0 — Migration\` documents the rewrite rules.
|
|
115
141
|
`;
|
|
116
|
-
const OPS = `#
|
|
142
|
+
const OPS = `# Ops reference — v0.7.0 canonical surface
|
|
143
|
+
|
|
144
|
+
Three op classes, two grammars:
|
|
145
|
+
|
|
146
|
+
| Class | Shape | When you reach for it |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| **Mutation statements** | \`$verb VAR = value\` / \`$verb VAR <value>\` | Bind / mutate a named variable in scope |
|
|
149
|
+
| **Runtime-intrinsic function-calls** | \`verb(kwarg=value, ...) [-> BINDING]\` | Language-intrinsic side-effects: emit, ask, file I/O, shell, composition |
|
|
150
|
+
| **External MCP dispatch** | \`$ <connector> kwarg=value, ... [-> BINDING]\` | Any tool resolved through \`connectors.json\` (LLM calls, memory queries, business tools) |
|
|
151
|
+
|
|
152
|
+
The \`$\` prefix marks **state-affecting** ops (mutation OR external dispatch). Function-call shape marks **language-intrinsic** ops the runtime knows directly. Legacy symbol forms (\`~\` / \`>\` / \`@\` / \`!\` / \`??\` / \`&\`) compile during the v0.7.x grace period with tier-2 \`deprecated-symbol-op\` warnings.
|
|
117
153
|
|
|
118
|
-
##
|
|
154
|
+
## Class 1: Mutation statements
|
|
119
155
|
|
|
120
|
-
###
|
|
156
|
+
### \`$set VAR = value\`
|
|
157
|
+
|
|
158
|
+
Bind a variable; runtime resolves \`\${REF}\` substitutions in the RHS at bind time (v0.5.0). Value can be a literal, a \`\${REF}\` interpolation, or a JSON literal (object / array / bool / null).
|
|
121
159
|
|
|
122
160
|
\`\`\`
|
|
123
|
-
$
|
|
124
|
-
$
|
|
161
|
+
$set GREETING = "Hello, \${USER}!"
|
|
162
|
+
$set ITEMS = []
|
|
163
|
+
$set CONFIG = {"timeout": 30, "retries": 3}
|
|
125
164
|
\`\`\`
|
|
126
165
|
|
|
127
|
-
|
|
128
|
-
\`primary\` MCP connector. Fallback binds when dispatch errors.
|
|
166
|
+
### \`$append VAR <value>\`
|
|
129
167
|
|
|
130
|
-
|
|
131
|
-
|
|
168
|
+
Append to a binding. v0.5.0 type-dispatches on the existing target:
|
|
169
|
+
- **List-typed target** → push (\`$set FOUND = []\` then \`$append FOUND \${ID}\`)
|
|
170
|
+
- **String-typed target** → concatenate (\`$set REPORT = ""\` then \`$append REPORT "more text"\`)
|
|
132
171
|
|
|
133
|
-
|
|
134
|
-
|------|---------|------|
|
|
135
|
-
| Bare string | \`status=open\` | string \`"open"\` |
|
|
136
|
-
| Quoted string | \`query="hello world"\` | string \`"hello world"\` (use when value contains whitespace) |
|
|
137
|
-
| Integer | \`limit=10\` | number \`10\` |
|
|
138
|
-
| Boolean | \`urgent=true\` | boolean \`true\` |
|
|
139
|
-
| Null | \`assignee=null\` | null |
|
|
140
|
-
| JSON array | \`tags=["a","b"]\` | array \`["a","b"]\` |
|
|
141
|
-
| JSON object | \`payload={"k":"v"}\` | object \`{"k":"v"}\` |
|
|
142
|
-
| Substitution | \`id=$(BUG_ID)\` | resolved at dispatch time |
|
|
143
|
-
| Quoted substitution | \`query="$(QUERY)"\` | quoted resolution (recommended when value may contain whitespace) |
|
|
172
|
+
Lint guards: \`uninitialized-append\` (no \`$set\` / \`# Vars:\` init); \`foreach-local-accumulator-target\` (init inside the same foreach as the append — silently loses data each iteration); \`append-to-non-list\` (numeric/boolean/null init).
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
174
|
+
\`\`\`
|
|
175
|
+
walk:
|
|
176
|
+
$set SEEN = []
|
|
177
|
+
$set REPORT = ""
|
|
178
|
+
foreach M in \${MESSAGES}:
|
|
179
|
+
if \${M.id} not in \${SEEN}:
|
|
180
|
+
$append SEEN \${M.id}
|
|
181
|
+
$append REPORT "\\n - \${M.id}: \${M.summary}"
|
|
182
|
+
\`\`\`
|
|
152
183
|
|
|
153
|
-
|
|
154
|
-
another stored skill end-to-end without requiring an MCP connector — pass
|
|
155
|
-
input vars as additional kwargs.
|
|
184
|
+
The append mutates the outer-scope binding (unlike \`$set\`, which is loop-local inside \`foreach\`).
|
|
156
185
|
|
|
157
|
-
|
|
158
|
-
and binds the structured value to \`P\`. Dotted descent via \`$(P.field)\`
|
|
159
|
-
works in conditions and emit — no filter+field grammar gymnastics.
|
|
186
|
+
## Class 2: Runtime-intrinsic function-calls
|
|
160
187
|
|
|
161
|
-
|
|
162
|
-
# Vars: PAYLOAD={"status":"ok","count":3}
|
|
188
|
+
Closed set: \`emit\`, \`ask\`, \`inline\`, \`execute_skill\`, \`shell\`, \`file_read\`, \`file_write\`. Unknown function-call names fire \`unknown-runtime-op\` tier-1 with remediation "if this is an MCP tool, use \`$ tool args -> R\` shape instead."
|
|
163
189
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
### \`emit(text="...")\` — output to skill consumer
|
|
191
|
+
|
|
192
|
+
One-line emission. \`\${VAR}\` substitutes. No result binding by default.
|
|
193
|
+
|
|
194
|
+
\`\`\`
|
|
195
|
+
emit(text="Hello, \${NAME}!")
|
|
196
|
+
emit(text="\${ISSUES.totalCount} open showstoppers in \${PROJECT}")
|
|
168
197
|
\`\`\`
|
|
169
198
|
|
|
170
|
-
|
|
171
|
-
the v0.3.2 \`|json_parse\` filter, which couldn't propagate parsed
|
|
172
|
-
structure through \`.field\` access.
|
|
199
|
+
### \`ask(prompt="...") -> R\` — prompt the user
|
|
173
200
|
|
|
174
|
-
|
|
201
|
+
Interactive only. Autonomous-mode dispatch fails with a clean error (use \`# Autonomous: true\` skill flag to disable the gate).
|
|
175
202
|
|
|
176
203
|
\`\`\`
|
|
177
|
-
|
|
204
|
+
ask(prompt="Proceed with auto-assignment for P0/P1?") -> APPROVAL
|
|
178
205
|
\`\`\`
|
|
179
206
|
|
|
180
|
-
|
|
181
|
-
default is \`default\`. Multi-line prompts via \`"..."\` are supported (parser
|
|
182
|
-
folds quoted-string continuations).
|
|
207
|
+
### \`shell(command="...", unsafe=true) [-> R]\` — local subprocess
|
|
183
208
|
|
|
184
|
-
|
|
209
|
+
Default mode: structural spawn — one binary, no shell metacharacters, no pipes/redirects. \`unsafe=true\` opts into full bash; tier-2 lint warns.
|
|
185
210
|
|
|
186
211
|
\`\`\`
|
|
187
|
-
|
|
212
|
+
shell(command="git status --porcelain") -> STATUS
|
|
213
|
+
shell(command="echo hi && date +%Y", unsafe=true) -> OUT
|
|
188
214
|
\`\`\`
|
|
189
215
|
|
|
190
|
-
|
|
191
|
-
(\`fts\` / \`semantic\` / \`rerank\` are common). \`limit\` is required.
|
|
216
|
+
### \`file_read(path="...") -> R\` — read file contents
|
|
192
217
|
|
|
193
|
-
|
|
218
|
+
Reads via Node \`fs.readFile\`. Substitutes \`\${VAR}\` in the path. Optional \`(fallback: "...")\` trailer binds when read fails. **Container note:** when the runtime is sandboxed (Docker, container deployment), the runtime's filesystem is namespace-isolated from the author's host — \`/tmp/x\` in the skill maps to the runtime's \`/tmp/x\`, not the host's. Use absolute paths under a known shared volume for cross-namespace work.
|
|
194
219
|
|
|
195
220
|
\`\`\`
|
|
196
|
-
|
|
197
|
-
@ unsafe full-shell-command -> VAR
|
|
221
|
+
file_read(path="/var/reports/today.md") -> REPORT (fallback: "no report")
|
|
198
222
|
\`\`\`
|
|
199
223
|
|
|
200
|
-
|
|
201
|
-
no pipes/redirects. \`@ unsafe\` opts into full bash; lint-flags tier-2.
|
|
224
|
+
### \`file_write(path="...", content="...", approved="...")\` — write file contents
|
|
202
225
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
### \`!\` — Emit text
|
|
226
|
+
Writes via Node \`fs.writeFile\`. Auto-creates parent directories. Substitutes \`\${VAR}\` in path + content. The \`approved="reason"\` kwarg authorizes the mutation per-op (any non-empty string; presence is what matters); skip when \`# Autonomous: true\` skill flag is declared. Same container FS-isolation caveat as \`file_read\` — the runtime's filesystem ≠ the author's.
|
|
206
227
|
|
|
207
228
|
\`\`\`
|
|
208
|
-
|
|
229
|
+
file_write(path="/var/reports/sweep-\${DATE}.md", content="\${REPORT}", approved="nightly cron deliverable")
|
|
209
230
|
\`\`\`
|
|
210
231
|
|
|
211
|
-
|
|
232
|
+
### \`inline(skill="...")\` — compile-time skill composition
|
|
212
233
|
|
|
213
|
-
|
|
234
|
+
References a \`# Type: data\` skill; the compiler inlines its emitted text at compile time so the compiled artifact is a single resolved document.
|
|
214
235
|
|
|
215
236
|
\`\`\`
|
|
216
|
-
|
|
237
|
+
inline(skill="common-prelude")
|
|
217
238
|
\`\`\`
|
|
218
239
|
|
|
219
|
-
|
|
240
|
+
### \`execute_skill(skill_name="...", ...kwargs) -> R\` — runtime skill composition
|
|
220
241
|
|
|
221
|
-
|
|
242
|
+
Invokes another stored skill end-to-end against the runtime's connectors. Returns the full execution record (final vars, transcript, outputs). Access via \`\${R.final_vars.FIELD}\`, \`\${R.transcript}\`, etc.
|
|
222
243
|
|
|
223
244
|
\`\`\`
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
execute_skill(skill_name="extract-json-number", JSON_BLOB="\${RAW}", FIELD_PATH="total_count") -> RESULT
|
|
246
|
+
emit(text="Extracted: \${RESULT.final_vars.VALUE|trim}")
|
|
226
247
|
\`\`\`
|
|
227
248
|
|
|
228
|
-
|
|
249
|
+
## Class 3: External MCP dispatch
|
|
229
250
|
|
|
230
251
|
\`\`\`
|
|
231
|
-
$
|
|
252
|
+
$ tool_name arg1=value1 arg2=value2 -> VAR [(fallback: "default")]
|
|
253
|
+
$ connector.tool_name args -> VAR
|
|
232
254
|
\`\`\`
|
|
233
255
|
|
|
234
|
-
|
|
256
|
+
Resolves the tool name against the adopter's \`connectors.json\`. Flat form (\`$ youtrack_search ...\`) uses the connector that owns the tool; dotted form (\`$ youtrack.search ...\`) routes explicitly. Fallback binds when dispatch errors. The substrate-specific shapes — LLM calls (\`$ llm\`), memory queries (\`$ memory\`), memory writes (\`$ memory_write\`), business tools — all use this dispatch.
|
|
257
|
+
|
|
258
|
+
**Kwarg value grammar.** Each \`key=value\` token follows a small literal grammar:
|
|
259
|
+
|
|
260
|
+
| Form | Example | Type |
|
|
261
|
+
|------|---------|------|
|
|
262
|
+
| Bare string | \`status=open\` | string \`"open"\` |
|
|
263
|
+
| Quoted string | \`query="hello world"\` | string \`"hello world"\` (use when value contains whitespace) |
|
|
264
|
+
| Integer | \`limit=10\` | number \`10\` |
|
|
265
|
+
| Boolean | \`urgent=true\` | boolean \`true\` |
|
|
266
|
+
| Null | \`assignee=null\` | null |
|
|
267
|
+
| JSON array | \`tags=["a","b"]\` | array \`["a","b"]\` |
|
|
268
|
+
| JSON object | \`payload={"k":"v"}\` | object \`{"k":"v"}\` |
|
|
269
|
+
| Substitution | \`id=\${BUG_ID}\` | resolved at dispatch time |
|
|
270
|
+
| Quoted substitution | \`query="\${QUERY}"\` | quoted resolution (recommended when value may contain whitespace) |
|
|
271
|
+
|
|
272
|
+
**v0.5.0 lint warning** \`unquoted-substitution-in-kwarg-value\` fires when an unquoted \`\${VAR}\` sits in kwarg-value position and VAR's binding origin suggests whitespace. Wrap as \`key="\${VAR}"\` to prevent silent arg truncation if the resolved value contains spaces.
|
|
273
|
+
|
|
274
|
+
**\`$ json_parse \${VAR} -> P\`** (v0.3.3) parses input as JSON and binds the structured value to \`P\`. Dotted descent via \`\${P.field}\` works in conditions and emit. Throws on malformed JSON (caught by \`else:\` / \`# OnError:\`).
|
|
235
275
|
|
|
236
276
|
\`\`\`
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
277
|
+
# Vars: PAYLOAD={"status":"ok","count":3}
|
|
278
|
+
|
|
279
|
+
read:
|
|
280
|
+
$ json_parse \${PAYLOAD} -> P
|
|
281
|
+
if \${P.status} == "ok" and \${P.count} > "0":
|
|
282
|
+
emit(text="processing \${P.count} items")
|
|
243
283
|
\`\`\`
|
|
244
284
|
|
|
245
|
-
|
|
285
|
+
## Substrate-portable LLM + memory dispatch
|
|
246
286
|
|
|
247
|
-
|
|
287
|
+
The canonical paths for LLM calls and memory queries are MCP dispatch through adopter-wired connectors. Connector names are convention — \`llm\` / \`memory\` / \`memory_write\` are descriptive, but adopters wire whatever names match their substrate.
|
|
248
288
|
|
|
249
289
|
\`\`\`
|
|
250
|
-
|
|
290
|
+
$ llm prompt="Classify priority: \${ISSUE.summary}" -> VERDICT
|
|
291
|
+
$ memory mode=fts query="recent incidents" limit=10 -> CONTEXT
|
|
292
|
+
$ memory_write content="\${REPORT}" addressed_to="oncall" tags="morning-sweep" approved="cron deliverable" -> R
|
|
251
293
|
\`\`\`
|
|
252
294
|
|
|
253
|
-
|
|
254
|
-
|
|
295
|
+
**Today's reality (v0.7.2).** Default deployments auto-wire \`llm\` + \`memory\` MCP connectors via bundled bridges (\`LocalModelMcpConnector\` over \`LocalModel\`; \`MemoryStoreMcpConnector\` over \`MemoryStore\`), so \`$ llm\` and \`$ memory\` work zero-config against the bundled Ollama + SQLite contracts. Adopters override by re-registering the same connector names against their own substrate. The legacy \`~ prompt=...\` and \`> mode=... query=...\` ops continue to dispatch through the bundled typed contracts during the v0.7.x grace period with tier-2 \`deprecated-symbol-op\` warnings. \`$ memory_write\` is deferred past v0.7.2 (requires \`MemoryStore.write\` interface, not yet shipped).
|
|
296
|
+
|
|
297
|
+
**One canonical call surface per concern.** \`$ memory\` is **the** memory-retrieval call surface — one contract (\`mode=... query=... limit=N -> R\` returning \`{items: [...]}\` envelope), one connector name. Both bare-form (\`$ memory ...\`) and dotted-form (\`$ memory.query ...\`) dispatch through the same registered connector. Same shape for \`$ llm\` (one \`prompt=... [maxTokens=N] [model="..."] -> R\` contract returning the response string). Author against the canonical \`$ llm\` / \`$ memory\` surfaces today; legacy \`~\` / \`>\` removal lands in v0.8/v0.9.
|
|
255
298
|
|
|
256
299
|
## Pipe filters
|
|
257
300
|
|
|
258
|
-
Apply on
|
|
301
|
+
Apply on \`\${VAR|filter}\` references; chain left-to-right.
|
|
259
302
|
|
|
260
303
|
| Filter | Effect |
|
|
261
304
|
|---|---|
|
|
@@ -264,33 +307,29 @@ Apply on \`$(VAR|filter)\` references; chain left-to-right.
|
|
|
264
307
|
| \`json\` | JSON.stringify |
|
|
265
308
|
| \`trim\` | Whitespace trim |
|
|
266
309
|
| \`length\` | Array element count or string char count (v0.2.5) |
|
|
267
|
-
| \`fallback:"X"\` | (v0.5.0) Coalesce-on-missing: when the upstream ref is unresolved, substitute literal \`X\` and continue the chain. Positional —
|
|
268
|
-
| \`isodate\` | (v0.5.0) Format an epoch timestamp (ms or sec, auto-detected by magnitude) as ISO-8601. Passes already-ISO strings through unchanged.
|
|
310
|
+
| \`fallback:"X"\` | (v0.5.0) Coalesce-on-missing: when the upstream ref is unresolved, substitute literal \`X\` and continue the chain. Positional — \`\${VAR|fallback:"-"|upper}\` defaults-then-uppercases. |
|
|
311
|
+
| \`isodate\` | (v0.5.0) Format an epoch timestamp (ms or sec, auto-detected by magnitude) as ISO-8601. Passes already-ISO strings through unchanged. \`\${EVENT.fired_at_unix|isodate}\`. |
|
|
269
312
|
|
|
270
|
-
**v0.5.0
|
|
271
|
-
the documented spec (was: raw epoch ms pre-v0.5.0 — a docs/runtime drift
|
|
272
|
-
identified by R3 minion 2). Numeric epoch values remain available as
|
|
273
|
-
\`$(EVENT.fired_at)\` (ms) and \`$(EVENT.fired_at_unix)\` (sec).
|
|
313
|
+
**\`\${NOW}\` ambient ref** substitutes as an ISO-8601 string per v0.5.0 spec. Numeric epoch values remain available as \`\${EVENT.fired_at}\` (ms) and \`\${EVENT.fired_at_unix}\` (sec).
|
|
274
314
|
|
|
275
315
|
## Conditional grammar
|
|
276
316
|
|
|
277
317
|
\`\`\`
|
|
278
|
-
if
|
|
279
|
-
if not
|
|
280
|
-
if
|
|
281
|
-
if
|
|
282
|
-
if
|
|
283
|
-
if
|
|
284
|
-
if
|
|
285
|
-
if
|
|
286
|
-
if
|
|
287
|
-
if
|
|
288
|
-
if
|
|
289
|
-
if not
|
|
318
|
+
if \${VAR}: ← truthy check
|
|
319
|
+
if not \${VAR}: ← falsy check (v0.3.2)
|
|
320
|
+
if \${VAR} == "literal": ← equality vs literal
|
|
321
|
+
if \${VAR} == \${OTHER}: ← equality vs ref
|
|
322
|
+
if \${VAR} != "literal": ← inequality
|
|
323
|
+
if \${N} < "10": ← numeric comparison (v0.2.5)
|
|
324
|
+
if \${N} >= \${THRESHOLD}: ← numeric vs ref
|
|
325
|
+
if \${M.id} in \${SEEN}: ← set membership
|
|
326
|
+
if \${M.id} not in \${SEEN}:
|
|
327
|
+
if \${A} == "ok" and \${B} == "ok": ← logical AND (v0.3.2)
|
|
328
|
+
if \${A} == "urgent" or \${B} > "5": ← logical OR (v0.3.2)
|
|
329
|
+
if not \${A} and (\${B} or \${C}): ← compound with parens + not (v0.3.2)
|
|
290
330
|
\`\`\`
|
|
291
331
|
|
|
292
|
-
Branches via \`if:\` / \`elif COND:\` / \`else:\`. The \`else:\` after a target
|
|
293
|
-
body is a separate error-handler block (distinguished by indentation scope).
|
|
332
|
+
Branches via \`if:\` / \`elif COND:\` / \`else:\`. The \`else:\` after a target body is a separate error-handler block (distinguished by indentation scope).
|
|
294
333
|
|
|
295
334
|
### Compound conditions (v0.3.2)
|
|
296
335
|
|
|
@@ -298,7 +337,23 @@ body is a separate error-handler block (distinguished by indentation scope).
|
|
|
298
337
|
|
|
299
338
|
- **Precedence** (tight → loose): comparison ops (\`==\`/\`<\`/etc.) > \`not\` > \`and\` > \`or\`
|
|
300
339
|
- **Parentheses** override precedence: \`(a or b) and c\`
|
|
301
|
-
- **Short-circuit evaluation**: AND skips RHS if LHS is false; OR skips RHS if LHS is true. Useful for the validate-then-access pattern — \`if
|
|
340
|
+
- **Short-circuit evaluation**: AND skips RHS if LHS is false; OR skips RHS if LHS is true. Useful for the validate-then-access pattern — \`if \${X} == "ok" and \${X.field} ...\` won't error on the field access when \`\${X} == "ok"\` is false.
|
|
341
|
+
|
|
342
|
+
## Legacy syntax (grace period — tier-2 deprecated)
|
|
343
|
+
|
|
344
|
+
| Legacy | Canonical |
|
|
345
|
+
|---|---|
|
|
346
|
+
| \`! text\` | \`emit(text="text")\` |
|
|
347
|
+
| \`?? "prompt" -> R\` | \`ask(prompt="prompt") -> R\` |
|
|
348
|
+
| \`@ cmd args [-> R]\` | \`shell(command="cmd args") [-> R]\` |
|
|
349
|
+
| \`@ unsafe cmd\` | \`shell(command="cmd", unsafe=true)\` |
|
|
350
|
+
| \`& skill-name\` | \`inline(skill="skill-name")\` |
|
|
351
|
+
| \`~ prompt="..." -> R\` | \`$ llm prompt="..." -> R\` (auto-wired via \`LocalModelMcpConnector\` bridge in default deployments) |
|
|
352
|
+
| \`> mode=... query=... -> R\` | \`$ memory mode=... query=... -> R\` (auto-wired via \`MemoryStoreMcpConnector\` bridge in default deployments) |
|
|
353
|
+
| \`$(VAR)\` | \`\${VAR}\` |
|
|
354
|
+
| \`(approved: "reason")\` trailer | \`approved="reason"\` kwarg |
|
|
355
|
+
|
|
356
|
+
All legacy forms compile during v0.7.x with tier-2 \`deprecated-symbol-op\` / \`deprecated-substitution-shape\` warnings. Tier-1 promotion (refuse-to-compile) lands in v0.8/v0.9.
|
|
302
357
|
`;
|
|
303
358
|
const FRONTMATTER = `# Frontmatter headers — full reference
|
|
304
359
|
|
|
@@ -312,7 +367,7 @@ Skill files open with \`# Key: value\` headers. Order isn't significant.
|
|
|
312
367
|
## Common
|
|
313
368
|
|
|
314
369
|
- \`# Description: <prose>\` — human-readable explanation; surfaces in dashboards.
|
|
315
|
-
- \`# Type: procedural | data\` — \`procedural\` (default) for runtime-fired skills; \`data\` for compile-time-inlined fragments referenced by
|
|
370
|
+
- \`# Type: procedural | data\` — \`procedural\` (default) for runtime-fired skills; \`data\` for compile-time-inlined fragments referenced by \`inline(skill="...")\` (canonical) or legacy \`& <skill-name>\` ops.
|
|
316
371
|
- \`# Vars: NAME=default, OTHER\` — declared variables. \`NAME=default\` provides a default; bare \`NAME\` is required at invocation.
|
|
317
372
|
- \`# Triggers: cron: 0 9 * * *, session: start\` — autonomous-dispatch sources. Comma-separated entries split by source-keyword boundary; cron expressions with commas (\`30,45 9 * * 1-5\`) parse correctly.
|
|
318
373
|
- \`# Output: text | slack: chan | prompt-context: agent | template: agent | file: path | card: id | none\` — output routing. **Value shape per kind (v0.5.0 clarification):** \`prompt-context:\` / \`template:\` / \`slack:\` / \`card:\` default to **joined emissions string** (the \`!\` lines concatenated with newlines) — these are human-readable delivery surfaces. \`text\` / \`file:\` default to the **last-bound variable value** (structured), falling back to the emissions array when no var was bound. If your skill emits multiple \`!\` lines and a downstream consumer only sees the final tool output via \`outputs.text\`, that's the structured-default behavior — use \`# Output: prompt-context: <agent>\` (or another text-coerced kind) to publish the joined emissions instead.
|
|
@@ -383,7 +438,7 @@ $(VAR.field|filter) field-access then filter
|
|
|
383
438
|
|
|
384
439
|
Unresolved refs: tier-1 \`undeclared-var\` at compile, \`UnresolvedVariableError\` at runtime.
|
|
385
440
|
`;
|
|
386
|
-
const EXAMPLES = `#
|
|
441
|
+
const EXAMPLES = `# Five canonical worked skills (v0.7.0+ canonical surface)
|
|
387
442
|
|
|
388
443
|
## 1. Minimal (single target, no dependencies)
|
|
389
444
|
|
|
@@ -394,13 +449,13 @@ const EXAMPLES = `# Three canonical worked skills
|
|
|
394
449
|
# Vars: WHO=world
|
|
395
450
|
|
|
396
451
|
greet:
|
|
397
|
-
|
|
398
|
-
|
|
452
|
+
emit(text="Hello, \${WHO}!")
|
|
453
|
+
emit(text="Welcome to Skillscript.")
|
|
399
454
|
|
|
400
455
|
default: greet
|
|
401
456
|
\`\`\`
|
|
402
457
|
|
|
403
|
-
Demonstrates: required headers, variable defaults,
|
|
458
|
+
Demonstrates: required headers, variable defaults, \`emit(text="...")\` with \`\${VAR}\` substitution.
|
|
404
459
|
|
|
405
460
|
## 2. Cron-fired numeric threshold + count
|
|
406
461
|
|
|
@@ -408,25 +463,27 @@ Demonstrates: required headers, variable defaults, \`!\` emission with substitut
|
|
|
408
463
|
# Skill: queue-length-monitor
|
|
409
464
|
# Description: Count pending items in a queue and alert when the count exceeds threshold
|
|
410
465
|
# Status: Approved
|
|
466
|
+
# Autonomous: true
|
|
411
467
|
# Vars: QUEUE_PATH=/var/queue/pending.json, THRESHOLD=10
|
|
412
468
|
# Triggers: cron: */5 * * * *
|
|
413
469
|
|
|
414
470
|
fetch:
|
|
415
|
-
|
|
471
|
+
file_read(path="\${QUEUE_PATH}") -> ITEMS_JSON (fallback: "[]")
|
|
472
|
+
$ json_parse \${ITEMS_JSON} -> ITEMS
|
|
416
473
|
|
|
417
474
|
evaluate:
|
|
418
475
|
needs: fetch
|
|
419
|
-
if
|
|
420
|
-
|
|
476
|
+
if \${ITEMS|length} > \${THRESHOLD}:
|
|
477
|
+
emit(text="Queue backlog: \${ITEMS|length} items pending (threshold \${THRESHOLD}). Action required.")
|
|
421
478
|
else:
|
|
422
|
-
|
|
479
|
+
emit(text="Queue healthy: \${ITEMS|length} items pending (under \${THRESHOLD}).")
|
|
423
480
|
|
|
424
481
|
default: evaluate
|
|
425
482
|
\`\`\`
|
|
426
483
|
|
|
427
|
-
Demonstrates: \`# Triggers:\` cron,
|
|
484
|
+
Demonstrates: \`# Triggers:\` cron, \`# Autonomous: true\` for unattended skills, \`file_read\` with fallback, \`$ json_parse\` for structured parsing, \`needs:\` body-line dep, numeric comparison, \`|length\` filter, \`if\` / \`else\`.
|
|
428
485
|
|
|
429
|
-
## 3.
|
|
486
|
+
## 3. LLM branching with agent delivery
|
|
430
487
|
|
|
431
488
|
\`\`\`
|
|
432
489
|
# Skill: classify-support-ticket
|
|
@@ -438,21 +495,21 @@ Demonstrates: \`# Triggers:\` cron, shell \`@\` op with fallback, \`needs:\` bod
|
|
|
438
495
|
# Output: prompt-context: oncall
|
|
439
496
|
|
|
440
497
|
classify:
|
|
441
|
-
|
|
498
|
+
$ llm prompt="Classify this support ticket as one of: 'critical', 'normal', 'low'. Reply with only the label. Ticket: \${TICKET_BODY}" -> VERDICT
|
|
442
499
|
|
|
443
500
|
route: classify
|
|
444
|
-
if
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
elif
|
|
448
|
-
|
|
501
|
+
if \${VERDICT|trim} == "critical":
|
|
502
|
+
emit(text="CRITICAL ticket needs immediate attention:")
|
|
503
|
+
emit(text="\${TICKET_BODY}")
|
|
504
|
+
elif \${VERDICT|trim} == "normal":
|
|
505
|
+
emit(text="Normal-priority ticket queued.")
|
|
449
506
|
else:
|
|
450
|
-
|
|
507
|
+
emit(text="Low-priority ticket logged.")
|
|
451
508
|
|
|
452
509
|
default: route
|
|
453
510
|
\`\`\`
|
|
454
511
|
|
|
455
|
-
Demonstrates:
|
|
512
|
+
Demonstrates: \`$ llm\` MCP dispatch (substrate-portable — adopter wires their LLM substrate under the \`llm\` connector name), \`|trim\` filter on LLM output, ref-vs-literal comparison, agent delivery via \`prompt-context:\`, augmenting headers (\`# Delivery-context:\` + \`# Templates:\`).
|
|
456
513
|
|
|
457
514
|
## 4. Composition — orchestrator invoking child skills
|
|
458
515
|
|
|
@@ -463,133 +520,151 @@ Demonstrates: \`~\` LocalModel op, named model selection, \`|trim\` filter on LL
|
|
|
463
520
|
# Vars: USER_NAME=Scott
|
|
464
521
|
|
|
465
522
|
gather:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
523
|
+
execute_skill(skill_name="calendar-today", USER="\${USER_NAME}") -> CAL (fallback: "(no calendar data)")
|
|
524
|
+
execute_skill(skill_name="mailbox-triage", USER="\${USER_NAME}") -> MAIL (fallback: "(mailbox empty)")
|
|
525
|
+
execute_skill(skill_name="weather-summary") -> WX (fallback: "(weather unavailable)")
|
|
469
526
|
|
|
470
527
|
render: gather
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
528
|
+
emit(text="Good morning, \${USER_NAME}. Today:")
|
|
529
|
+
emit(text="• Calendar: \${CAL}")
|
|
530
|
+
emit(text="• Mailbox: \${MAIL}")
|
|
531
|
+
emit(text="• Weather: \${WX}")
|
|
475
532
|
|
|
476
533
|
default: render
|
|
477
534
|
\`\`\`
|
|
478
535
|
|
|
479
|
-
Demonstrates:
|
|
536
|
+
Demonstrates: \`execute_skill(...)\` runtime composition (each child runs through the runtime under a depth-counted chain), per-call \`(fallback: ...)\` for resilience, kwarg forwarding, \`->\` binding child output for downstream reference.
|
|
480
537
|
|
|
481
|
-
## 5. Dedup-by-id with the accumulator (v0.3.0)
|
|
538
|
+
## 5. Dedup-by-id with the accumulator (v0.3.0+)
|
|
482
539
|
|
|
483
540
|
\`\`\`
|
|
484
541
|
# Skill: dedup-walk
|
|
485
542
|
# Description: Walk a result list, skip items whose id was already seen.
|
|
486
543
|
# Status: Approved
|
|
544
|
+
# Vars: TOPIC=infrastructure
|
|
487
545
|
|
|
488
546
|
walk:
|
|
489
|
-
|
|
547
|
+
$ memory mode=topical query="\${TOPIC}" limit=50 -> CANDIDATES
|
|
490
548
|
$set SEEN = []
|
|
491
|
-
foreach C in
|
|
492
|
-
if
|
|
493
|
-
$append SEEN
|
|
494
|
-
|
|
549
|
+
foreach C in \${CANDIDATES.items}:
|
|
550
|
+
if \${C.id} not in \${SEEN}:
|
|
551
|
+
$append SEEN \${C.id}
|
|
552
|
+
emit(text="NEW: \${C.id} — \${C.summary}")
|
|
495
553
|
else:
|
|
496
|
-
|
|
497
|
-
|
|
554
|
+
emit(text="dup: \${C.id}")
|
|
555
|
+
emit(text="Total novel items: \${SEEN|length}")
|
|
498
556
|
|
|
499
557
|
default: walk
|
|
500
558
|
\`\`\`
|
|
501
559
|
|
|
502
|
-
Demonstrates: \`$append\` accumulator pattern, \`$set SEEN = []\` init at the target body (before the foreach) so mutations persist across iterations, \`not in\` membership check against the accumulating list, \`|length\` filter on the final collected list.
|
|
503
|
-
`;
|
|
504
|
-
const COMPOSITION = `# Composition
|
|
560
|
+
Demonstrates: \`$ memory\` MCP dispatch (substrate-portable memory query), \`$append\` accumulator pattern, \`$set SEEN = []\` init at the target body (before the foreach) so mutations persist across iterations, \`not in\` membership check against the accumulating list, \`|length\` filter on the final collected list. **Note** — most MCP memory tools wrap the array in an envelope object (e.g., \`{items: [...], hasNextPage}\`); the example assumes \`.items\` is the array field. Check your tool's response shape; tier-3 \`object-iteration-advisory\` lint helps when you forget the field accessor.
|
|
505
561
|
|
|
506
|
-
|
|
562
|
+
## Triggered cron deliverable — memory handoff
|
|
507
563
|
|
|
508
|
-
|
|
564
|
+
\`\`\`
|
|
565
|
+
# Skill: morning-showstopper-sweep
|
|
566
|
+
# Description: Cron pre-triage; delivers triaged showstoppers to oncall via prompt-context
|
|
567
|
+
# Status: Approved
|
|
568
|
+
# Autonomous: true
|
|
569
|
+
# Vars: PROJECT=INFRA
|
|
570
|
+
# Triggers: cron: 0 8 * * MON-FRI
|
|
571
|
+
# Output: prompt-context: oncall
|
|
509
572
|
|
|
510
|
-
|
|
573
|
+
run:
|
|
574
|
+
$ ticketing_search query="project:\${PROJECT} severity:showstopper state:Open" limit=20 -> ISSUES
|
|
511
575
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
576
|
+
emit(text="Morning showstoppers for \${PROJECT} — \${ISSUES.totalCount} open:")
|
|
577
|
+
foreach ISSUE in \${ISSUES.items}:
|
|
578
|
+
$ llm prompt="Two-line triage hypothesis for: \${ISSUE.summary}" -> ANALYSIS
|
|
579
|
+
emit(text="")
|
|
580
|
+
emit(text="## \${ISSUE.id}: \${ISSUE.summary}")
|
|
581
|
+
emit(text="\${ANALYSIS}")
|
|
582
|
+
|
|
583
|
+
default: run
|
|
516
584
|
\`\`\`
|
|
517
585
|
|
|
518
|
-
-
|
|
519
|
-
|
|
520
|
-
|
|
586
|
+
Demonstrates: end-to-end trigger → process → deliver pattern. Trigger fires cron; process pulls data + sub-classifies each issue with \`$ llm\`; delivers via prompt-context channel (each \`emit(text=...)\` becomes a line in the agent's session-start context).
|
|
587
|
+
`;
|
|
588
|
+
const COMPOSITION = `# Composition — composing skills from other skills
|
|
589
|
+
|
|
590
|
+
Skillscript has two composition primitives in v0.7.0+ canonical form. Both let one skill draw on another's output, with different semantics around when the child runs.
|
|
521
591
|
|
|
522
|
-
##
|
|
592
|
+
## 1. \`inline(skill="<name>")\` — compile-time data-skill inline
|
|
523
593
|
|
|
524
|
-
|
|
594
|
+
Inlines an *Approved data skill* into the host skill's compiled artifact at the call site. The data skill's body becomes part of the rendered prompt. Use for *static* knowledge or templated content (style guides, voice rules, runbooks).
|
|
525
595
|
|
|
526
596
|
\`\`\`
|
|
527
|
-
|
|
528
|
-
|
|
597
|
+
brief:
|
|
598
|
+
$ llm prompt="\${VOICE_RULES} Now write a one-line status:" -> RESULT
|
|
599
|
+
inline(skill="voice-rules")
|
|
529
600
|
\`\`\`
|
|
530
601
|
|
|
531
|
-
-
|
|
532
|
-
-
|
|
602
|
+
- Resolved at \`compile()\` time — the data skill's \`content_hash\` is recorded in the host's provenance block.
|
|
603
|
+
- Provenance lets \`skillfile audit\` detect stale recompiles when a referenced data skill changes.
|
|
604
|
+
- The data skill must be marked \`# Type: data\` (or live in a path the SkillStore recognizes as data); otherwise it's treated as procedural and won't inline.
|
|
533
605
|
|
|
534
|
-
##
|
|
606
|
+
## 2. \`execute_skill(skill_name="<child>", ...kwargs) -> R\` — runtime invocation
|
|
535
607
|
|
|
536
|
-
The
|
|
608
|
+
The general composition form: the host calls another skill at runtime, capturing its full execution record. Same depth-counted chain (default 5) as the recursion guard.
|
|
537
609
|
|
|
538
610
|
\`\`\`
|
|
539
611
|
gather:
|
|
540
|
-
|
|
541
|
-
|
|
612
|
+
execute_skill(skill_name="calendar-today", USER="\${USER_NAME}") -> CAL (fallback: "(no calendar data)")
|
|
613
|
+
execute_skill(skill_name="mailbox-triage", inputs={"USER": "\${USER_NAME}"}) -> MAIL
|
|
542
614
|
\`\`\`
|
|
543
615
|
|
|
544
|
-
Two kwarg styles, both supported (v0.2.9
|
|
545
|
-
- **Bare kwargs** — \`USER
|
|
546
|
-
- **\`inputs={...}\` JSON** —
|
|
616
|
+
Two kwarg-forwarding styles, both supported (v0.2.9):
|
|
617
|
+
- **Bare kwargs** — \`USER="\${USER_NAME}"\` natural skill grammar
|
|
618
|
+
- **\`inputs={...}\` JSON** — useful when forwarding many fields verbatim
|
|
547
619
|
|
|
548
|
-
The bound \`->
|
|
620
|
+
The bound \`-> R\` carries the child's full execution record (final_vars, transcript, outputs) into the host's scope. Access via \`\${R.final_vars.FIELD}\`, \`\${R.transcript}\`, \`\${R.outputs.text}\`, etc.
|
|
549
621
|
|
|
550
622
|
## Limits & lint signals
|
|
551
623
|
|
|
552
|
-
- **Recursion**: depth-5 chain by default (\`ExecuteSkillRecursionError\` if exceeded).
|
|
553
|
-
- **Lint** (\`unknown-skill-reference\`, tier-2 as of v0.3.1):
|
|
624
|
+
- **Recursion**: depth-5 chain by default (\`ExecuteSkillRecursionError\` if exceeded).
|
|
625
|
+
- **Lint** (\`unknown-skill-reference\`, tier-2 as of v0.3.1): both \`inline(skill="<name>")\` and \`execute_skill(skill_name="<name>", ...)\` validate the child exists in the SkillStore at compile time. Forward references are allowed: missing skills lint as warning (not error), runtime throws \`MissingSkillReferenceError\` if still unresolved at execute. Tier-3 \`deferred-skill-reference\` advisory confirms when the deferred-resolution path is engaged.
|
|
554
626
|
- **Lint** (\`disabled-skill-reference\`, tier-1): any composition primitive pointing at a \`# Status: Disabled\` skill blocks compile.
|
|
555
627
|
|
|
556
628
|
## When to use which
|
|
557
629
|
|
|
558
630
|
| Use case | Primitive |
|
|
559
631
|
|---|---|
|
|
560
|
-
| Static knowledge in a prompt |
|
|
561
|
-
|
|
|
562
|
-
|
|
563
|
-
|
|
632
|
+
| Static knowledge in a prompt | \`inline(skill="<data-skill>")\` |
|
|
633
|
+
| Child output bound into parent scope | \`execute_skill(skill_name="<skill>", ...) -> R\` |
|
|
634
|
+
|
|
635
|
+
## Legacy forms (grace period)
|
|
636
|
+
|
|
637
|
+
- \`& <skill-name>\` → \`inline(skill="<skill-name>")\` (compile-time inline)
|
|
638
|
+
- \`& invoke <skill-name>\` (removed concept) → \`execute_skill(skill_name="<skill-name>")\`
|
|
639
|
+
- \`$ execute_skill skill_name="<child>" ... -> R\` → \`execute_skill(skill_name="<child>", ...) -> R\` (legacy MCP-dispatch shape still compiles during grace; canonical is the function-call shape)
|
|
564
640
|
|
|
565
641
|
See \`help({topic: "examples"})\` example 4 for a worked orchestrator skill.
|
|
566
642
|
`;
|
|
567
643
|
const CONNECTORS_PROLOGUE = `# Connectors
|
|
568
644
|
|
|
569
|
-
|
|
570
|
-
typed registry of five contracts:
|
|
645
|
+
Skillscript skills don't import packages — they invoke connectors. The runtime resolves dispatches through a typed registry of five contracts:
|
|
571
646
|
|
|
572
|
-
| Contract | Purpose | Op |
|
|
647
|
+
| Contract | Purpose | Op surface |
|
|
573
648
|
|---|---|---|
|
|
574
|
-
| SkillStore | Skill source persistence + status lifecycle | (
|
|
575
|
-
|
|
|
576
|
-
|
|
|
577
|
-
| McpConnector | MCP tool dispatch |
|
|
578
|
-
| AgentConnector | Deliver augment/template payloads | \`# Output: prompt-context:\` / \`template:\` |
|
|
649
|
+
| \`SkillStore\` | Skill source persistence + status lifecycle | implicit (\`inline\` / \`execute_skill\` reference) |
|
|
650
|
+
| \`LocalModel\` | LLM inference (Ollama by default) | \`$ llm\` MCP dispatch via auto-wired \`LocalModelMcpConnector\` bridge (v0.7.2); legacy \`~\` op during grace period |
|
|
651
|
+
| \`MemoryStore\` | Knowledge retrieval (SQLite-FTS by default) | \`$ memory\` MCP dispatch via auto-wired \`MemoryStoreMcpConnector\` bridge (v0.7.2); legacy \`>\` op during grace period |
|
|
652
|
+
| \`McpConnector\` | MCP tool dispatch — all external tools | \`$ <connector_name> args\` |
|
|
653
|
+
| \`AgentConnector\` | Deliver augment/template payloads | \`# Output: prompt-context:\` / \`template:\` |
|
|
654
|
+
|
|
655
|
+
**v0.7.2 substrate framing.** Canonical syntax routes substrate-specific dispatch through MCP (\`$ llm\` / \`$ memory\` rather than legacy \`~\` / \`>\`). Default deployments auto-wire the \`llm\` and \`memory\` connector names via bundled bridges (\`LocalModelMcpConnector\` over \`LocalModel\`; \`MemoryStoreMcpConnector\` over \`MemoryStore\`), so \`$ llm\` and \`$ memory\` work zero-config. Adopters override by re-registering those same connector names against their own substrate (e.g., a hosted-model MCP server in place of the local Ollama bridge); the canonical call sites don't change.
|
|
656
|
+
|
|
657
|
+
**One canonical call surface per concern.** \`$ memory\` is **the** memory-retrieval call surface — one contract (\`mode=... query=... limit=N -> R\` returning \`{items: [...]}\` envelope), one connector name. Both bare-form (\`$ memory ...\`) and dotted-form (\`$ memory.query ...\`) dispatch through the same registered connector. Same shape for \`$ llm\` (one \`prompt=... [maxTokens=N] [model="..."] -> R\` contract returning the response string). The legacy \`~\` / \`>\` removal lands in v0.8/v0.9 — author against the canonical \`$ llm\` / \`$ memory\` surfaces today.
|
|
658
|
+
|
|
659
|
+
## Discovery
|
|
579
660
|
|
|
580
|
-
|
|
581
|
-
this runtime, plus their feature flags, is discoverable via:
|
|
661
|
+
\`runtime_capabilities()\` reports the live picture: which connectors are registered, which feature flags they advertise, and which named instances exist (e.g., \`default\` / \`qwen\` LocalModels, \`youtrack\` McpConnector).
|
|
582
662
|
|
|
583
|
-
|
|
663
|
+
For shell execution (\`shell(...)\` op), \`runtime_capabilities\` also reports \`shellExecution.mode\` (\`"structural-spawn"\`) and \`shellExecution.unsafe_enabled\` (whether \`shell(command=..., unsafe=true)\` / legacy \`@ unsafe\` is permitted in this deployment).
|
|
584
664
|
|
|
585
|
-
|
|
586
|
-
which feature flags they advertise, and which named instances exist
|
|
587
|
-
(e.g., \`default\` / \`qwen\` LocalModels).
|
|
665
|
+
## Container filesystem isolation
|
|
588
666
|
|
|
589
|
-
|
|
590
|
-
\`shellExecution.mode\` (\`"structural-spawn"\`) and
|
|
591
|
-
\`shellExecution.unsafe_enabled\` (whether \`@ unsafe\` is permitted in
|
|
592
|
-
this deployment).
|
|
667
|
+
When the runtime is sandboxed (Docker container, deployed VM, etc.), the runtime's filesystem is namespace-isolated from the author's host. \`file_read("/tmp/x")\` and \`file_write(path="/tmp/x", ...)\` operate on the *runtime's* \`/tmp\`, not the host's. For cross-namespace work, use a known shared volume path or expose the file via a mount point both sides see. \`runtime_capabilities()\` (planned v0.8+) will report writable base paths to make this discoverable from cold-author position.
|
|
593
668
|
`;
|
|
594
669
|
const LINT_CODES = `# Lint rule index
|
|
595
670
|
|
|
@@ -627,12 +702,14 @@ Three tiers per ERD §3:
|
|
|
627
702
|
## Tier-2 (warning)
|
|
628
703
|
|
|
629
704
|
- \`deprecated-question\` — bare \`?\` op (deprecated v1; compile-error in v1.x)
|
|
705
|
+
- \`deprecated-symbol-op\` (v0.7.1) — legacy symbol-form op (\`~\`, \`>\`, \`@\`, \`!\`, \`??\`, \`&\`) compiles but warns with canonical replacement. Tier-1 promotion (refuse-to-compile) lands in v0.8/v0.9.
|
|
706
|
+
- \`deprecated-substitution-shape\` (v0.7.1) — \`$(VAR)\` substitution form compiles but warns; rewrite to \`\${VAR}\`. Tier-1 promotion in v0.8/v0.9.
|
|
630
707
|
- \`unsafe-shell-ambiguous-subst\` — \`$(NAME)\` inside \`@ unsafe\` body that isn't a declared variable; collides with bash command-sub syntax
|
|
631
708
|
- \`unsafe-shell-op\` — \`@ unsafe\` op present; requires human review every time
|
|
632
709
|
- \`unknown-retrieval-arg\` — \`>\` op carries kwargs outside mode/query/limit/connector/fallback (v0.2.12 Bug 26)
|
|
633
710
|
- \`unknown-skill-reference\` — \`&\` or \`$ execute_skill\` references a skill not in the store (demoted from tier-1 in v0.3.1; runtime throws \`MissingSkillReferenceError\` if still unresolved at execute)
|
|
634
711
|
- \`unknown-template-reference\` — \`# Templates: <name>\` references a skill not in the store (demoted from tier-1 in v0.3.1)
|
|
635
|
-
- \`unconfirmed-mutation\` —
|
|
712
|
+
- \`unconfirmed-mutation\` — mutation-class op (\`$\` tool with mutating-name shape, \`$ memory_write\`, \`file_write(...)\`) runs without authorization. v0.7.0+ accepts the captured \`approved="reason"\` per-op kwarg as authorization (any non-empty string; presence is what matters). Silent when the skill declares \`# Autonomous: true\` (v0.4.2 — the autonomous-skill category exempts the rule since the user-confirmation pattern doesn't apply to unattended-execution skills) or when a preceding \`??\` / \`ask(...)\` op gates the mutation in the same target.
|
|
636
713
|
- \`model-contention\` — async + sync ops on the same model serialize on a single runtime worker
|
|
637
714
|
- \`draft-with-trigger\` — \`# Status: Draft\` skill has \`# Triggers:\` declared; triggers won't fire until Approved
|
|
638
715
|
- \`reference-to-disabled-skill\` — \`&\` op references a Disabled skill (also tier-1 in some contexts)
|
|
@@ -645,6 +722,7 @@ Three tiers per ERD §3:
|
|
|
645
722
|
- \`plugin-collision\` — placeholder for v1.x plugin-loader name conflicts
|
|
646
723
|
- \`deferred-skill-reference\` — composition ref (\`&\` / \`$ execute_skill\` / \`# Templates:\`) targets a skill not currently in the SkillStore; resolution deferred to execute time (v0.3.1+). Confirms the forward-reference path is engaged; clears once the target is stored.
|
|
647
724
|
- \`unparsed-json-field-access\` — op text contains \`$(VAR|json_parse).field\`; the \`|json_parse\` filter was removed in v0.3.3. Replace with \`$ json_parse $(VAR) -> P\` then \`$(P.field)\`.
|
|
725
|
+
- \`object-iteration-advisory\` (v0.7.2) — \`foreach IT in \${VAR}\` iterates a bound variable whose origin is a \`$\` MCP tool output, without a \`.field\` accessor. MCP tools commonly wrap arrays in an envelope object (\`.items\`, \`.results\`, \`.issuesPage\`, \`.data\`, \`.records\`). Check the tool's response shape; rewrite as \`foreach IT in \${VAR.items}\` (or the correct field). Placeholder for v0.8 tool-schema introspection that catches this precisely.
|
|
648
726
|
- \`disallowed-tool\` (tier-1, v0.4.1) — \`$ name.tool\` references a tool not in the connector's \`allowed_tools\` allowlist. Either rewrite the skill to use a permitted tool or update \`connectors.json\` to grant access. Runtime defense-in-depth refuses disallowed dispatch even if lint is bypassed.
|
|
649
727
|
|
|
650
728
|
\`compile_skill({source})\` runs the full lint preflight and reports
|