skillscript-runtime 0.7.0 → 0.7.3
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 +86 -19
- package/dist/bootstrap.d.ts +11 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +45 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.js +25 -13
- package/dist/cli.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 +21 -1
- package/dist/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +49 -4
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/index.d.ts +4 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +11 -0
- package/dist/connectors/index.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 +321 -239
- package/dist/help-content.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +302 -49
- package/dist/lint.js.map +1 -1
- package/dist/parser.d.ts +23 -3
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +121 -9
- package/dist/parser.js.map +1 -1
- package/dist/runtime-config.d.ts +56 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +145 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +15 -7
- package/dist/runtime.js.map +1 -1
- package/examples/classify-support-ticket.skill.md +1 -1
- package/examples/custom-bootstrap.example.ts +130 -0
- package/examples/feedback-sentiment-scan.skill.md +0 -1
- package/examples/hello.skill.provenance.json +1 -1
- package/examples/morning-brief.skill.md +2 -4
- package/examples/onboarding-scaffold/README.md +80 -0
- package/examples/onboarding-scaffold/bootstrap.ts +100 -0
- package/examples/onboarding-scaffold/connectors.json +15 -0
- package/examples/onboarding-scaffold/file-memory-store.ts +95 -0
- package/examples/onboarding-scaffold/memory.example.json +23 -0
- package/examples/onboarding-scaffold/openai-local-model.ts +117 -0
- package/examples/onboarding-scaffold/tmux-shell-agent-connector.ts +110 -0
- 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
|
+
| **File handoff** | \`file_write(path="...", content="...")\` | Skill writes a file at a known location for the agent to read |
|
|
37
|
+
| **Memory handoff** *(v0.8.x)* | \`$ memory_write content="..." recipients=["agent"] -> R\` | Skill writes a memory the target agent picks up via mailbox. **Deferred to v0.8.x** bundled with the auth model; not available in v0.7.x. |
|
|
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
|
-
|
|
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."
|
|
189
|
+
|
|
190
|
+
### \`emit(text="...")\` — output to skill consumer
|
|
191
|
+
|
|
192
|
+
One-line emission. \`\${VAR}\` substitutes. No result binding by default.
|
|
163
193
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
! processing $(P.count) items
|
|
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.
|
|
202
|
-
|
|
203
|
-
## Emission + control ops
|
|
224
|
+
### \`file_write(path="...", content="...", approved="...")\` — write file contents
|
|
204
225
|
|
|
205
|
-
|
|
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.3).** 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 to v0.8.x** bundled with the auth model — \`MemoryStore.write()\` contract extension + passthrough credential threading ship together so the documented memory-handoff delivery channel matches reality.
|
|
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,10 +367,10 @@ 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
|
-
- \`# Output: text |
|
|
373
|
+
- \`# Output: text | prompt-context: agent | template: agent | file: path | none\` — output routing. Five kinds, all substrate-neutral. **Value shape per kind:** \`prompt-context:\` / \`template:\` default to **joined emissions string** (the \`emit(text=...)\` lines concatenated with newlines) — human-readable agent-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. **For substrate-specific delivery destinations** (Slack, WhatsApp, Discord, pagerduty, custom dashboards, etc.) — that's contract-between-the-skill-and-the-substrate territory, downstream of the language. Two paths: (1) \`$ <connector>.<tool> ...\` inside the skill body to dispatch through an adopter-wired MCP connector, or (2) deliver via \`prompt-context:\` to an agent that decides how to surface the result.
|
|
319
374
|
- \`# OnError: <fallback-skill-name>\` — error-handler skill invoked when an op fails and no target-level \`else:\` catches.
|
|
320
375
|
- \`# Autonomous: true | false\` — declarative authorship intent for unattended-execution skills (cron-fired, agent-fired, etc.). v0.4.2. Today silences \`unconfirmed-mutation\` lint warnings for the whole skill (since the user-confirmation pattern doesn't apply to autonomous skills); reserved as the canonical autonomous-skill category marker for future rules + scheduling defaults + discovery surfaces. Omitted = interactive (default).
|
|
321
376
|
|
|
@@ -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,155 @@ 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
|
|
521
589
|
|
|
522
|
-
|
|
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.
|
|
523
591
|
|
|
524
|
-
|
|
592
|
+
## 1. \`inline(skill="<name>")\` — compile-time data-skill inline
|
|
593
|
+
|
|
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.3 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
|
+
**Adopter-extensible class registration (v0.7.3).** Custom \`McpConnector\` classes that are JSON-instantiable register via \`registerConnectorClass(name, entry)\` from adopter bootstrap before \`loadConnectorsConfig\` runs. Replaces the pre-v0.7.3 pattern of editing the bundled \`KNOWN_CONNECTOR_CLASSES\` Map directly (merge-conflict bait). See \`examples/custom-bootstrap.example.ts\`.
|
|
658
|
+
|
|
659
|
+
**Canonical runtime config (v0.7.3).** \`skillscript.config.json\` externalizes runtime knobs (skillsDir, traceDir, dashboard port, etc.) so the two-instance posture (dev + adopter on same machine) works as copy-and-tweak. CLI flags override file values; file values override defaults. See \`skillscript.config.json.example\`.
|
|
660
|
+
|
|
661
|
+
**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.
|
|
662
|
+
|
|
663
|
+
## Discovery
|
|
579
664
|
|
|
580
|
-
|
|
581
|
-
this runtime, plus their feature flags, is discoverable via:
|
|
665
|
+
\`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
666
|
|
|
583
|
-
|
|
667
|
+
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
668
|
|
|
585
|
-
|
|
586
|
-
which feature flags they advertise, and which named instances exist
|
|
587
|
-
(e.g., \`default\` / \`qwen\` LocalModels).
|
|
669
|
+
## Container filesystem isolation
|
|
588
670
|
|
|
589
|
-
|
|
590
|
-
\`shellExecution.mode\` (\`"structural-spawn"\`) and
|
|
591
|
-
\`shellExecution.unsafe_enabled\` (whether \`@ unsafe\` is permitted in
|
|
592
|
-
this deployment).
|
|
671
|
+
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
672
|
`;
|
|
594
673
|
const LINT_CODES = `# Lint rule index
|
|
595
674
|
|
|
@@ -627,12 +706,14 @@ Three tiers per ERD §3:
|
|
|
627
706
|
## Tier-2 (warning)
|
|
628
707
|
|
|
629
708
|
- \`deprecated-question\` — bare \`?\` op (deprecated v1; compile-error in v1.x)
|
|
709
|
+
- \`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.
|
|
710
|
+
- \`deprecated-substitution-shape\` (v0.7.1) — \`$(VAR)\` substitution form compiles but warns; rewrite to \`\${VAR}\`. Tier-1 promotion in v0.8/v0.9.
|
|
630
711
|
- \`unsafe-shell-ambiguous-subst\` — \`$(NAME)\` inside \`@ unsafe\` body that isn't a declared variable; collides with bash command-sub syntax
|
|
631
712
|
- \`unsafe-shell-op\` — \`@ unsafe\` op present; requires human review every time
|
|
632
713
|
- \`unknown-retrieval-arg\` — \`>\` op carries kwargs outside mode/query/limit/connector/fallback (v0.2.12 Bug 26)
|
|
633
714
|
- \`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
715
|
- \`unknown-template-reference\` — \`# Templates: <name>\` references a skill not in the store (demoted from tier-1 in v0.3.1)
|
|
635
|
-
- \`unconfirmed-mutation\` —
|
|
716
|
+
- \`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
717
|
- \`model-contention\` — async + sync ops on the same model serialize on a single runtime worker
|
|
637
718
|
- \`draft-with-trigger\` — \`# Status: Draft\` skill has \`# Triggers:\` declared; triggers won't fire until Approved
|
|
638
719
|
- \`reference-to-disabled-skill\` — \`&\` op references a Disabled skill (also tier-1 in some contexts)
|
|
@@ -645,6 +726,7 @@ Three tiers per ERD §3:
|
|
|
645
726
|
- \`plugin-collision\` — placeholder for v1.x plugin-loader name conflicts
|
|
646
727
|
- \`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
728
|
- \`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)\`.
|
|
729
|
+
- \`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
730
|
- \`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
731
|
|
|
650
732
|
\`compile_skill({source})\` runs the full lint preflight and reports
|