Release history
Skillscript releases
All releases
110 shown
Parser error on orphan ops + missing entry target
- Run `skillfile lint` pre‑upgrade to locate skills using removed trigger sources.
- Consult `docs/adopter-playbook.md` § "Trigger model" for migration patterns (POST to `/event`).
- Update skill declarations: replace removed source types with an external adapter that POSTs to `/event`.
- Removed trigger source axis: dropped `session`, `agent-event`, `file-watch`, and `sensor` – only `cron` and `event` remain.
- DeliveryMeta enum entry `trigger_kind: "session"` removed.
- `scheduler.fireSessionPhase` API removed.
- `event_name` trigger primitive (case‑insensitive) for declarative event handling
- `POST /event` HTTP ingress on DashboardServer, configurable via env vars and optional bearer‑token auth
- Strict param validation per Perry's spec – every declared param must be present with no unknown params
Full changelog
Closes the locked Scott + Perry decision (memory ceaf4579): collapse
triggers to two primitives. Time-based (cron) + external-signal
(event via HTTP POST). Everything else is an external adapter that
POSTs to /event.
BREAKING
Trigger source axis trimmed. Pre-v0.19.0 the runtime accepted 6
sources (cron / session / event / agent-event / file-watch /
sensor); 4 of those were parse-only stubs that never functionally
fired. v0.19.0 keeps only cron and event. Skills declaring removed
sources fail to parse with a tier-1 parse error.
Migration: run skillfile lint against your corpus pre-upgrade to
find skills with removed sources. Each removed source has a
documented replacement pattern (external adapter POSTs to /event) —
see docs/adopter-playbook.md § "Trigger model".
DeliveryMeta trigger_kind: "session" removed from the enum. The
v0.9.6-locked receiver contract drops "session" from
origin.trigger_kind. Per Scott's call: the value was a non-feature
(session triggers never functionally emitted), and pre-v1.0 makes the
cleanup cheap. Adopter substrates handling DeliveryMeta payloads with
literal "session" trigger_kind will get a TypeScript narrowing error
on rebuild — drop the dead case.
scheduler.fireSessionPhase removed. Boot/shutdown session
dispatch is gone. Adopters wanting boot-time orchestration POST to
/event from their startup script.
New surfaces
event_name trigger primitive. Skills declare event triggers via
# Triggers: event: <event_name> (case-insensitive). External
posters address the event_name (the public contract); the
skill_name is private impl. 1:1 — exactly one skill per
event_name; cross-skill rebind allowed but audit-logged
(event_name 'X' rebound: skill A → skill B). Same-skill re-register
is silent upsert (declarative re-save path).
POST /event HTTP ingress on DashboardServer. Off by default
(SKILLSCRIPT_EVENT_INGRESS_ENABLED=true to opt in). Shares the
dashboard port — no separate port. Localhost-only by default; explicit
opt-in for 0.0.0.0. Optional bearer-token auth via
SKILLSCRIPT_EVENT_INGRESS_AUTH_TOKEN. Wire contract:
POST /event HTTP/1.1
Authorization: Bearer <token> # if configured
Content-Type: application/json
{ "event_name": "ping", "params": { "NAME": "world" } }
→ 200 { "run_id": "<uuid>", "durability": "in-process" }
404 unknown event_name | route disabled
400 missing/extra params | malformed body
401 missing/wrong auth
405 wrong HTTP method
Strict param validation (v1): every declared param must be
present; no unknown params accepted. Per Perry's spec: no defaults,
no type coercion in v1 (JSON carries types; type mismatches fail
inside consuming ops with real errors). Defaults + types deferred to
v2 if real adopter need surfaces. Params are auto-derived from the
skill's # Vars: declaration for event triggers.
run_id = trace_id (preMintedTraceId plumbed through). The
synchronous run_id in the 200 response matches the trace_id the
runtime writes to the trace store — paste run_id into the dashboard
or query via the trace store to look up completion status.
Self-describing durability: every successful response includes
durability: "in-process". Honest contract: 200 = accepted into
THIS process's in-memory queue; best-effort / at-most-once / NOT
durable across restart. Durable queueing is a v2 follow-up.
Operator surfaces
Two new env vars (config-cascade-aware per existing pattern):
| Env var | Effect | Default |
|---|---|---|
| SKILLSCRIPT_EVENT_INGRESS_ENABLED=true | Mount POST /event route | false |
| SKILLSCRIPT_EVENT_INGRESS_AUTH_TOKEN=<token> | Bearer-token auth (when set, required on every POST) | unset (open-internally) |
Tests
21 new tests in v0.19.0-trigger-event-ring.test.ts:
Scheduler.fireEvent(6): happy + 404 + case-insensitive + missing + extra + no-params- Registration (2): same-skill silent upsert + cross-skill audit log
POST /eventHTTP (11): disabled-404, GET-405, 200-with-run_id-durability, unknown-404, missing-400, extra-400, malformed-400, empty-event_name-400, 401-no-auth, 401-wrong-token, 200-correct-token- Config validation (1): throws if enabled without scheduler
- MCP register_trigger (1): cron + event accepted
Plus 4 existing test files updated to v0.19.0 enum (cron+event only) +
new parse-error tests for removed sources. 1791 tests total; typecheck
clean.
Live HTTP probe verified end-to-end
Built dist, ran skillfile serve with event ingress enabled, POSTed
real curl requests:
- Valid POST → 200 + run_id matching the trace file written to disk
- 404 / 400 / 401 responses all match the spec
- run_id
f3495f73-...written totraces/event-probe/f3495f73-....json
Probe caught two gaps the unit tests didn't surface:
- Event triggers needed
paramsauto-derived from skill# Vars:
(declarative wiring path) dispatchSkillneeded to thread event params into compile-time
inputs (required-var check fired pre-execute)
Both fixed in the same ring; banking-worthy reminder of "build + probe
end-to-end before claiming done" discipline.
Docs
docs/adopter-playbook.md— new "Trigger model (v0.19.0 — BREAKING)"
section: removed-source migration patterns +eventprimitive shape- wire contract +
event_namesemantics + enabling + durability
honesty
- wire contract +
docs/configuration.md— env-var table extended with
SKILLSCRIPT_EVENT_INGRESS_*scaffold/.env.example— new "Event-trigger HTTP ingress" section;
clarifies SKILLSCRIPT_PORT is shared with the dashboardREADME.md— MCP tool surface mentionsPOST /eventopt-in
Language-reference rewrite is Perry's atom (forward-banner in place
since 05f24be0); landed when the v0.19.0 ring shipped clean.
What's NOT in v0.19.0 (future / deferred)
- File-watch / sensor reference adapter stubs — scope-cut per Scott's
call (memoryb9fd8e17). Replaced by canonical "calling /event"
example in Perry's upcoming language-reference rewrite. - Param defaults + type validation — deferred to v2 per Perry's spec.
- Durable / at-least-once event queue — v2 if real adopter need
surfaces; in-memory v1 self-describes viadurabilityfield. c7ddfc50body-text-as-output template ring — design-locked, queued
for v0.19.1+ after trigger ring stabilizes.
WakeReceipt honesty + session targeting
Agent identity threading for skill authoring
Identity‑propagation level 2 + store status preservation
Init seeding + dashboard hint + SQLite warning suppression
Mutation enforcement + filter strictness
- `MemoryStore` → `DataStore` (contract name)
- `MemoryStoreClass` → `DataStoreClass`
- `MemoryStoreCapabilities` → `DataStoreCapabilities`
Full changelog
Pre-adoption window means breaking changes are still cheap. MemoryStore was
misleading nomenclature — it imported cognitive / agent-mental-state semantics
from AMP (the Tradita-internal substrate where this code originated) into what
is actually a generic data-persistence contract. Adopters without an AMP-like
memory broker had to mentally translate every time. Cost compounds per adopter;
pay once now.
The mental model after rename:
| Contract | What it does | Adopter intuition |
|---|---|---|
| SkillStore | Named, versioned, lifecycle-tracked code artifacts | "My skill catalog / code repo / playbook library" |
| DataStore | Generic key/value persistence with query | "My data store / KV store / search index" |
| Richer semantics (vector retrieval, memory broker, etc.) | Wire via MCP, not via skillscript contract | "Bring my own substrate" |
Both contracts describe what they DO. The asymmetry we documented in v0.13.8
(SkillStore is mutable/versioned CRUD vs append-only search log) is now
obvious-from-the-name, not a design stance the doc has to call out.
Breaking changes (every external identifier renamed; hard rename, no aliases)
Contract + types:
MemoryStore→DataStoreMemoryStoreClass→DataStoreClassMemoryStoreCapabilities→DataStoreCapabilitiesMemoryStoreManifest→DataStoreManifestMemoryStoreFeature→DataStoreFeatureMemoryWrite→DataWriteMemoryWriteRecord→DataWriteRecordPortableMemory→PortableDataCuratedMemoryField→CuratedDataFieldCURATED_MEMORY_FIELDS→CURATED_DATA_FIELDS
Bundled impls + bridges + templates:
SqliteMemoryStore→SqliteDataStoreMemoryStoreMcpConnector→DataStoreMcpConnectorMemoryStoreTemplate(fork-me skeleton) →DataStoreTemplate
Registry methods:
registerMemoryStore→registerDataStoregetMemoryStore→getDataStoregetMemoryStoreClass→getDataStoreClasslistMemoryStores→listDataStoreslistMemoryStoreClasses→listDataStoreClasseshasMemoryStore→hasDataStore
Config + env + filesystem:
connectors.jsonsubstrate key:memory_store→data_store- Env var:
MEMORY_DB→DATA_DB - Bootstrap option:
memoryDbPath→dataDbPath - Default db filename:
<SKILLSCRIPT_HOME>/memory.db→<SKILLSCRIPT_HOME>/data.db runtime_capabilitiesfield:memoryStores→dataStores
Ops (skill source) + MCP tools:
$ memory ...read op →$ data_read ...$ memory_write ...write op →$ data_write ...- MCP tool:
memory_read→data_read - Internally-registered MCP connector instance names:
memory→data_read,
memory_write→data_write
Source files:
src/connectors/memory-store.ts→data-store.tssrc/connectors/memory-store-mcp.ts→data-store-mcp.tsexamples/connectors/MemoryStoreTemplate/→DataStoreTemplate/examples/onboarding-scaffold/file-memory-store.ts→file-data-store.ts
NOT renamed (intentional — naming is honest)
SkillStore(andSqliteSkillStore,FilesystemSkillStore, etc.) — skills
ARE skills. The misleading-naming pattern was specific to memory-store
importing agent-cognition semantics into generic data persistence.AgentConnector(and the HTTP webhook bridge) — substrate-neutral delivery
to a frontier agent; name honestly describes the function.docs/sqlite-skill-store.mdfilename — documentsSqliteSkillStorewhich
stays named. (Perry's initial rename mapping had this as a typo; corrected.)language-reference.mdrename pass — separate doc lane, Perry's work.
Adopter migration
This is a one-PR rename for any adopter who's forked a connector template. The
patterns are mechanical: every Memory identifier in your code becomes Data;
every $ memory op in your skills becomes $ data_read; every
registerMemoryStore call becomes registerDataStore; etc.
The Phase 2/3 cold-adopter AmpMemoryStore becomes AmpDataStore in the same
fashion. ~5 LOC of identifier renames per file plus the constructor/import
updates.
Why now
[[project_pre_adoption_rule]]: pre-adoption window is cheap; aliases + soft
deprecation are more total cost than one painful PR. SkillStore + DataStore is
the mental model we'd want a v1.0 reader to see immediately. Pay once now.
- Update any code that consumes the flat `SkillMeta[]` array from `skill_list` to handle the new `SkillCatalog` shape.
- Adjust dashboard components (e.g., bundled UI) to flatten the grouped response for table display if still required.
- Changed `skill_list` response from `SkillMeta[]` (flat array) to `SkillCatalog` object with `receives`, `skills`, and `headless` properties.
- Added category derivation logic for `augmenting`, `template`, and `headless` skill types based on output kinds.
- Introduced `SkillListFilter` interface supporting AND-composed audience, status, trigger_kind, domain_tags, and name_prefix filters.
- Implemented vars rendering rules converting frontmatter syntax (`NAME`, `NAME=value`, `NAME=`) into structured `{name, required, default}` entries.
Full changelog
Per Perry's audit thread f0b8b832 + addendum 73c79a28 + lock 011feaf0.
skill_list returns a pre-grouped SkillCatalog so cold agents reading at
session start see immediately "what pushes to me" vs "what I can invoke."
Wire shape changes (BREAKING — pre-adoption rule)
skill_list response shape changed from SkillMeta[] (flat array) to
SkillCatalog (pre-grouped object):
interface SkillCatalog {
receives?: SkillEntry[]; // augmenting (# Output: agent:)
skills?: SkillEntry[]; // template (# Output: template:) + agent-invokable
headless?: SkillEntry[]; // no agent/template output; present when audience filter allows
}
interface SkillEntry {
name: string;
category: "augmenting" | "template" | "headless";
description: string;
status: SkillStatus;
vars: Array<{ name: string; required: boolean; default: string | null }>;
output: Array<{ kind: "agent" | "template" | "text" | "file" | "none"; target?: string }>;
triggers: Array<
| { kind: "cron"; expression: string }
| { kind: "session"; phase: "start" | "end" }
| { kind: "webhook"; path?: string }
| { kind: "event"; event_type: string }
>;
}
Category derivation
From existing # Output: semantics — no new frontmatter syntax. Multi-output rule:
ANY output[i].kind === "agent" → "augmenting" (surfaces in `receives`)
else ANY output[i].kind === "template" → "template" (surfaces in `skills`)
else → "headless" (surfaces only when audience filter allows)
Filter mechanism (AND-composed)
interface SkillListFilter {
audience?: "agent" | "all" | "headless"; // default "agent"
status?: SkillStatus; // default "Approved"
trigger_kind?: "cron" | "session" | "webhook" | "event";
domain_tags?: string[]; // AND-match
name_prefix?: string; // adopter-side scoping
}
Vars rendering (per addendum 73c79a28)
| # Vars: frontmatter | Rendered entry |
|---|---|
| NAME (bare) | { name: "NAME", required: true, default: null } |
| NAME=value | { name: "NAME", required: false, default: "value" } |
| NAME= (equals, empty value) | { name: "NAME", required: false, default: "" } |
Footnote pinned in SkillEntry doc-comment
"Invocation is independent of discovery grouping. execute_skill(skill_name="X")
works regardless of whether X surfaced in receives or skills. Discovery
grouping is signal, not gating."
Added
src/skill-catalog.ts—buildSkillCatalog()+ category derivation +
vars/outputs/triggers rendering helpers (~160 LOC; auxiliary surface, not narrow-core)src/connectors/types.ts—SkillCatalog,SkillEntry,SkillListFilterinterfacestests/v0.9.8-skill-catalog.test.ts— locked-shape coverage (category derivation,
vars rendering, triggers union, filter composition, audience grouping, Q2 footnote)
Migration
mcp__skillscript__skill_listcallers consuming the flat-array shape break.
Pre-adoption rule applies; no shipped adopters.- Bundled dashboard updated to flatten the grouped response for its existing
table view.
LOC ceiling
Bumped narrow-core 8550 → 8650 for the new contract types in connectors/types.ts.
Fourth bump in v0.9.x; consolidate-first signal stands but new contract surface
isn't consolidatable.
OutputKind rename + notify + memory_write
- Update skills using literal backslash sequences (`\n`, `\t`) in double‑quoted strings to match new escape semantics.
- Lints now suggest `$ llm` and `$ memory` for deprecated symbol ops; adjust remediation messages accordingly.
- String escape interpretation in double‑quoted strings changes behavior (\n, \t, \\, \" now produce actual characters) — pre‑adoption rule applies; existing skills using literal \n bytes must be rewritten.
- Triple‑quote multi‑line string literals (`"""...""") for prose‑shaped content.
- `${VAR}` substitution in `# Output:` target slot enabling compile‑time input resolution.
- Bridge classes: `LocalModelMcpConnector`, `MemoryStoreMcpConnector` with auto‑wire at bootstrap.
Full changelog
R4-driven punchlist + bridge classes. Closes the cold-author findings
from the R4 harness round (Perry's report d284763f, Scott's decisions
d89905f3, bridge-class scope-lock 831c2661, Perry's GO 5f471b0a).
The hypothesis test passed in R4 — minions reached for canonical
emit(), file_write(), ${VAR} naturally; friction moved deeper into
substantive language semantics. v0.7.2 closes the substantive friction
and lands the substrate-portability story end-to-end.
Added — language semantics
-
String escape interpretation in double-quoted strings.
\n,\t,
\\,\"interpret to their actual chars inside"...". Bash /
Python / JS / Go / C all do this; skillscript joins the prior. R4
minion 4 was reaching for@ printf %b "${VAR}"as a workaround;
now$set X = "line1\nline2"produces real newlines. Single-quoted
strings stay literal pass-through (reserved for v0.8+ literal
semantics). Breaking change — pre-adoption rule applies (no
external users to disrupt); any skill relying on literal\nbytes
needs a one-time rewrite. -
Triple-quote multi-line string literals.
"""..."""for prose-
shaped content. Spans line breaks naturally; embedded single"
doesn't terminate (three consecutive"chars don't accidentally
appear in natural English). Escape interpretation applies same as
single double-quote. Use cases: long-formemit(text="""..."""),
file_write(content="""..."""), multi-line$set X = """..."""
reports. -
${VAR}substitution in# Output:target slot. Compile-time
inputs resolution (caller-passedinputstocompile_skill,
# Vars:defaults,# Requires:cascade values). Runtime-bound
refs (from$op outputs) explicitly deferred — needs two-phase
frontmatter resolution architectural call. Closes R4 finding #6
(minion 5 wrote# Output: prompt-context: ${TARGET_AGENT}expecting
parameterized routing; now works).
Added — lints
-
Tier-3
object-iteration-advisorylint. R4's strongest signal
(4 of 5 minions hit it). Fires onforeach IT in ${VAR}where VAR's
binding origin is a$MCP tool output without.fieldaccessor.
Hints at common envelope-field names (.items,.results,
.issuesPage,.data,.records). Placeholder for v0.8 tool-schema
introspection that catches this precisely. -
unconfirmed-mutationbroadened to legacy@ops. Closes R4
minion 4 sub-finding:@ printf %b ${REPORT}silently word-split
whenREPORTcontained whitespace. Lint now flags any suspect
${VAR}substitution in@op bodies (same origin-detection logic
as the existing$op coverage). Pattern also widened to recognize
both$(VAR)and${VAR}forms.
Added — bridge classes (substrate-portability lands)
-
LocalModelMcpConnector. Bridge class that exposes a registered
LocalModelinstance as anMcpConnector. WrapsLocalModel.run
per the canonical contract:
$ <connector> prompt="..." [maxTokens=N] [model="..."] -> Rwhere
R is the model's response string. -
MemoryStoreMcpConnector. Bridge class that exposes a registered
MemoryStoreinstance as anMcpConnector. WrapsMemoryStore.query:
$ <connector> mode="fts|semantic|rerank" query="..." limit=N [...extras] -> Rwhere R ={items: PortableMemory[]}envelope
(consistent with the object-iteration-advisory's hint pattern). -
Bootstrap auto-wire. Bridges wire automatically at bootstrap as
connector instancesllm(default LocalModel) +memory(default
MemoryStore, when SQLite db exists). Zero-config —$ llm prompt="..."
and$ memory mode=fts query="..." limit=10work in default
deployments without adopter wiring. Adopters override by re-registering
the names or wiring entries inconnectors.json.
Architectural framing — canonical MCP-dispatch contract
v0.7.2 doesn't just ship bridge code — it defines what $ llm and
$ memory MEAN in skillscript by shipping with explicit kwarg surfaces.
Two layers of substrate portability:
LocalModel+MemoryStoreinterface contracts (typed contracts
within the runtime). Adopters implement these to plug in their
substrate without writing MCP servers. BundledOllamaLocalModel+
SqliteMemoryStoreare reference impls.- MCP dispatch via
$ <name>— bridge classes expose Layer 1 as
MCP; adopters can also bypass bridges and wire any MCP server under
any name ($ pinecone_vector,$ amp.amp_query_memories, etc.).
Bundled memory surface is ONE canonical call (per Perry's scope-lock
5f471b0a):
$ memory mode="fts|semantic|rerank" query="..." limit=N [...extras] -> R
Read-only, FTS-flavored. Substrate-portable across any MemoryStore-
interface impl. Explicitly not bundled (adopter wires via dotted-form
escape hatch $ <connector>.<tool>):
$ memory_write,$ memory_get(no MemoryStore interface methods)- Thread operations (
$ thread_get,$ thread_close,
$ thread_check_mailbox, etc.) — substrate-specific - Introspection / traversal / promote / reinforce — substrate-specific
- Mutations beyond write — substrate-specific
For Tradita-style deployments, AMP wires as $ amp.<tool> with the
full ~15-tool surface available; $ memory covers the canonical query
path, $ amp.<tool> covers AMP-specific operations.
Changed
-
deprecated-symbol-oplint — remediation messages now confidently
suggest$ llm/$ memory(the bridge auto-wire makes these
load-bearing in default deployments). No more "(or your wired LLM
connector name)" caveat. -
help()content — all six topics (quickstart, ops, frontmatter,
examples, composition, connectors, lint-codes) refreshed to canonical
v0.7.0+ surface. Container FS isolation note added.object-iteration- advisoryindexed. Tradita-internal naming scrubbed from connectors
topic. -
Quickstart hero example — broken
$append REPORT <line>...</line>
accumulator pattern replaced withemit(text="...")per-line +
# Output: prompt-context:delivery channel. Matches Perry's
corrected §1 doc atom. -
AST:
op.sourceForm?: "function-call"field already added in v0.7.1
to distinguish canonical from legacy at lint time. Continues to do
load-bearing work for the deprecated-symbol-op lint.
LOC ceiling
Narrow-core ceiling 7250 → 7550. Bridges add ~80 LOC each (auxiliary
surface). String-escape interpreter + triple-quote tokenizer state +
${VAR}-in-Output substitution + object-iteration advisory + @-op
unquoted-subst extension add ~200 LOC narrow-core total.
Tests
53 new tests across 5 v0.7.2-specific test files:
v0.7.2-object-iteration-advisory.test.ts— 6 testsv0.7.2-string-escapes.test.ts— 12 testsv0.7.2-triple-quote-literals.test.ts— 14 testsv0.7.2-output-substitution.test.ts— 9 testsv0.7.2-unquoted-subst-at-op.test.ts— 7 testsv0.7.2-bridge-classes.test.ts— 16 tests (unit + closed-set registry)
Plus updated fixtures across test files where bootstrap auto-wire
shifted expected behavior. Suite: 974 passing, 10 skipped, 1 env-gated
YouTrack.
Deferred to v0.8 (per locked roadmap)
- Tool-schema introspection that adapts the object-iteration-advisory
- deprecation lints based on actual connector availability
- Richer memory contract covering vector / embedding / hybrid
substrates beyond FTS-flavored query MemoryStore.write()interface addition to enable a bundled
$ memory_writebridge (currently adopter-wired)- Pagination /
whileloop primitive (v0.9) - Phase 2 trigger sources (event + agent-event) — v0.11
- Output routers (slack + card) — v0.12
Breaking changes — review before upgrading.
NOW ISO‑8601, append type‑dispatch, connector error