skillscript-runtime 0.14.0 → 0.14.1

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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/connectors/data-store-mcp.d.ts +9 -0
  3. package/dist/connectors/data-store-mcp.d.ts.map +1 -1
  4. package/dist/connectors/data-store-mcp.js +25 -0
  5. package/dist/connectors/data-store-mcp.js.map +1 -1
  6. package/dist/connectors/filter-enforcement.d.ts +18 -0
  7. package/dist/connectors/filter-enforcement.d.ts.map +1 -0
  8. package/dist/connectors/filter-enforcement.js +57 -0
  9. package/dist/connectors/filter-enforcement.js.map +1 -0
  10. package/dist/connectors/types.d.ts +13 -0
  11. package/dist/connectors/types.d.ts.map +1 -1
  12. package/dist/connectors/types.js.map +1 -1
  13. package/dist/errors.d.ts +42 -0
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +66 -0
  16. package/dist/errors.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/lint.d.ts.map +1 -1
  22. package/dist/lint.js +33 -41
  23. package/dist/lint.js.map +1 -1
  24. package/dist/mcp-server.d.ts.map +1 -1
  25. package/dist/mcp-server.js +9 -6
  26. package/dist/mcp-server.js.map +1 -1
  27. package/dist/mutation-gate.d.ts +61 -0
  28. package/dist/mutation-gate.d.ts.map +1 -0
  29. package/dist/mutation-gate.js +89 -0
  30. package/dist/mutation-gate.js.map +1 -0
  31. package/dist/runtime.d.ts.map +1 -1
  32. package/dist/runtime.js +53 -11
  33. package/dist/runtime.js.map +1 -1
  34. package/docs/adopter-playbook.md +76 -32
  35. package/docs/sqlite-skill-store.md +1 -1
  36. package/examples/connectors/DataStoreTemplate/DataStoreTemplate.ts +22 -9
  37. package/examples/connectors/DataStoreTemplate/README.md +6 -6
  38. package/examples/connectors/McpConnectorTemplate/README.md +1 -1
  39. package/examples/custom-bootstrap.example.ts +10 -3
  40. package/examples/onboarding-scaffold/README.md +3 -4
  41. package/examples/onboarding-scaffold/bootstrap.ts +11 -6
  42. package/examples/onboarding-scaffold/connectors.json +1 -1
  43. package/examples/onboarding-scaffold/file-data-store.ts +18 -7
  44. package/examples/programmatic-trace-demo.mjs +1 -1
  45. package/examples/skillscripts/classify-support-ticket.skill.md +9 -8
  46. package/examples/skillscripts/cut-release-tag.skill.md +6 -6
  47. package/examples/skillscripts/doc-qa-with-citations.skill.md +2 -2
  48. package/examples/skillscripts/feedback-sentiment-scan.skill.md +8 -7
  49. package/examples/skillscripts/hello.skill.provenance.json +1 -1
  50. package/examples/skillscripts/service-health-watch.skill.md +5 -4
  51. package/package.json +1 -1
  52. package/scaffold/config.toml +4 -2
  53. /package/examples/onboarding-scaffold/{memory.example.json → data.example.json} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  A skeleton `DataStore` implementation for adopters writing their own. Not runnable; every method throws a `TODO` error. Copy this directory, rename, fill in the substrate-specific work.
4
4
 
5
- Use this when you want skillscript memories backed by:
5
+ Use this when you want skillscript data persistence backed by:
6
6
  - A vector database (Pinecone, Weaviate, Qdrant, Chroma)
7
7
  - A data store backing (memory broker like AMP, hosted memory API, vector DB, etc.)
8
8
  - A different SQL flavor (Postgres + pgvector, MySQL)
@@ -18,7 +18,7 @@ DataStore (choose which connector)
18
18
  └── Your fork from this template
19
19
  ```
20
20
 
21
- The runtime is substrate-agnostic. Memories don't know which backend they're stored against — the contract is the `DataStore` interface, and any class implementing it works.
21
+ The runtime is substrate-agnostic. Data records don't know which backend they're stored against — the contract is the `DataStore` interface, and any class implementing it works.
22
22
 
23
23
  ## Forking workflow
24
24
 
@@ -72,8 +72,8 @@ The DataStore contract is narrower than SkillStore. Three methods + `staticCapab
72
72
 
73
73
  | Method | What it does | When called |
74
74
  |---|---|---|
75
- | `query(filters)` | Read memories by mode + filter; return `PortableData[]` | Every `$ data_read mode=... query=...` op |
76
- | `write(entry)` | Persist a new memory; return `{id, created_at}` | Every `$ data_write content=... -> R` op + `data_write` skill notify routes |
75
+ | `query(filters)` | Read records by mode + filter; return `PortableData[]` | Every `$ data_read mode=... query=...` op |
76
+ | `write(entry)` | Persist a new record; return `{id, created_at}` | Every `$ data_write content=... -> R` op + `data_write` skill notify routes |
77
77
  | `manifest()` | Capability snapshot for `runtime_capabilities` discovery | At startup + on-demand from MCP clients |
78
78
 
79
79
  Per the curated-subset framing in `src/connectors/types.ts`, `PortableData` has a 4-tier field model:
@@ -113,7 +113,7 @@ Honor what your substrate supports; ignore the rest. Document the supported set
113
113
 
114
114
  The `DataWrite` shape:
115
115
 
116
- - **`content`** (required) — the memory body
116
+ - **`content`** (required) — the record body
117
117
  - **`tags`** (optional) — routed to your substrate's tag mechanism
118
118
  - **`recipients`** (optional) — advisory hint; substrates with alerting (e.g., AMP's mailbox model) use it
119
119
  - **`expires_at`** (optional) — unix seconds; substrates with TTL honor it
@@ -121,7 +121,7 @@ The `DataWrite` shape:
121
121
 
122
122
  Return `{id, created_at}` — the substrate-assigned identifier + creation timestamp (unix seconds).
123
123
 
124
- If your substrate doesn't support writes (read-only memory like a search index over a static corpus), set `supports_writes: false` in `staticCapabilities()` and throw from `write()`. The runtime + lint will respect the flag.
124
+ If your substrate doesn't support writes (read-only store like a search index over a static corpus), set `supports_writes: false` in `staticCapabilities()` and throw from `write()`. The runtime + lint will respect the flag.
125
125
 
126
126
  ## Wiring against the dashboard / MCP
127
127
 
@@ -139,7 +139,7 @@ The declarative path is the canonical adopter pattern for stdio-bridged remote M
139
139
  | Cardinality | Many instances per deployment | One singleton per slot |
140
140
  | Substrate config | Per-instance via top-level keys | `substrate` section short/object/custom |
141
141
  | Class extensibility | `registerConnectorClass()` for adopter-custom classes | Programmatic bootstrap (or `substrate.skill_store: {type: "custom", ...}` once async-bootstrap lands) |
142
- | Auto-wired bridges | `llm` + `memory` + `data_write` (LocalModel + DataStore exposed via bridge connectors) | n/a (these ARE the substrates being bridged) |
142
+ | Auto-wired bridges | `llm` + `data_read` + `data_write` (LocalModel + DataStore exposed via bridge connectors) | n/a (these ARE the substrates being bridged) |
143
143
 
144
144
  McpConnector is fundamentally the "dispatch to external tools" surface — narrowest contract, broadest range of impls.
145
145
 
@@ -86,14 +86,21 @@ registry.registerSkillStore("primary", skillStore);
86
86
  // Wire your own LocalModel, DataStore, AgentConnector here. For typed-
87
87
  // contract impls, this is the "case 1" wiring — substrate-portable.
88
88
  // registry.registerLocalModel("default", new MyHostedLlmAdapter(...));
89
- // registry.registerDataStore("primary", new MyMemoryAdapter(...));
89
+ // registry.registerDataStore("primary", new MyDataStoreAdapter(...));
90
90
  // registry.registerAgentConnector("primary", new MyAgentHarnessAdapter(...));
91
91
 
92
- // Then wire bridges if you want the canonical `$ llm` / `$ data_read` surfaces:
92
+ // Then wire bridges if you want the canonical `$ llm` / `$ data_read` /
93
+ // `$ data_write` surfaces. The DataStore bridge handles BOTH read and
94
+ // write via toolName discrimination; register the same instance under both
95
+ // connector names so bare-form name-match resolution picks the right route.
93
96
  // import { LocalModelMcpConnector, DataStoreMcpConnector } from "skillscript-runtime";
94
97
  // registry.registerMcpConnector("llm", new LocalModelMcpConnector(registry.getLocalModel("default")));
95
98
  // const ms = registry.listDataStores().find((e) => e.name === "primary");
96
- // if (ms !== undefined) registry.registerMcpConnector("data_read", new DataStoreMcpConnector(ms.instance));
99
+ // if (ms !== undefined) {
100
+ // const dataBridge = new DataStoreMcpConnector(ms.instance);
101
+ // registry.registerMcpConnector("data_read", dataBridge);
102
+ // registry.registerMcpConnector("data_write", dataBridge);
103
+ // }
97
104
 
98
105
  // Wire any connectors.json instances.
99
106
  for (const c of connectors) {
@@ -1,4 +1,4 @@
1
- # Onboarding scaffold — file-backed memory + OpenAI + tmux-shell
1
+ # Onboarding scaffold — file-backed data store + OpenAI + tmux-shell
2
2
 
3
3
  A complete adopter-deployment example demonstrating substrate-portable wiring for skillscript-runtime. **~200 LOC across three adapter files** plus bootstrap. Copy this directory and modify for your own deployment.
4
4
 
@@ -25,7 +25,7 @@ cd ~/my-skillscript-deployment
25
25
  # 3. Set up env
26
26
  export SKILLSCRIPT_HOME=$(pwd)
27
27
  export OPENAI_API_KEY=sk-...
28
- cp memory.example.json memory.json # initial memory
28
+ cp data.example.json data.json # initial data store seed
29
29
 
30
30
  # 4. Init skillscript dir layout
31
31
  skillfile init --here
@@ -49,7 +49,7 @@ node --loader ts-node/esm bootstrap.ts
49
49
  | `tmux-shell-agent-connector.ts` | `AgentConnector` impl — tmux send-keys | ~75 |
50
50
  | `bootstrap.ts` | Wiring — Registry, bridges, scheduler, MCP server | ~75 |
51
51
  | `connectors.json` | Example adopter-MCP wiring (empty by default) | — |
52
- | `memory.example.json` | Seed memory file with three example records | — |
52
+ | `data.example.json` | Seed data file with three example records | — |
53
53
 
54
54
  ## Two-instance posture
55
55
 
@@ -77,4 +77,3 @@ The skill bodies don't need to change. `$ llm prompt=...` keeps working; `$ data
77
77
 
78
78
  - **Adopter playbook** — `docs/adopter-playbook.md` walks through Case 1 vs Case 2 wiring patterns
79
79
  - **Custom bootstrap walkthrough** — `examples/custom-bootstrap.example.ts` shows registering custom McpConnector classes via `registerConnectorClass`
80
- - **v0.8.x roadmap** — `$ data_write` ships in v0.8.x bundled with the auth model. When that lands, extend `FileDataStore` with the corresponding `write()` method.
@@ -1,4 +1,4 @@
1
- // Onboarding scaffold: complete bootstrap wiring file-backed memory +
1
+ // Onboarding scaffold: complete bootstrap wiring file-backed data store +
2
2
  // OpenAI LLM + tmux-shell AgentConnector. v0.7.3.
3
3
  //
4
4
  // Copy this file into your deployment and modify substrate choices to
@@ -43,9 +43,9 @@ const registry = new Registry();
43
43
  const skillStore = new FilesystemSkillStore(config.skillsDir ?? `${HOME}/skills`);
44
44
  registry.registerSkillStore("primary", skillStore);
45
45
 
46
- // File-backed memory at $SKILLSCRIPT_HOME/memory.json
46
+ // File-backed data store at $SKILLSCRIPT_HOME/data.json
47
47
  const dataStore = new FileDataStore({
48
- filePath: config.dataDbPath ?? `${HOME}/memory.json`,
48
+ filePath: config.dataDbPath ?? `${HOME}/data.json`,
49
49
  });
50
50
  registry.registerDataStore("primary", dataStore);
51
51
 
@@ -63,10 +63,15 @@ const agentConnector = new TmuxShellAgentConnector({
63
63
  });
64
64
  registry.registerAgentConnector("primary", agentConnector);
65
65
 
66
- // Step 3: wire bridges so `$ llm` / `$ data_read` dispatch through the
67
- // adopter substrates above (case 1 typed-contract wiring — portable).
66
+ // Step 3: wire bridges so `$ llm` / `$ data_read` / `$ data_write` dispatch
67
+ // through the adopter substrates above (case 1 typed-contract wiring —
68
+ // portable). The DataStore bridge handles BOTH read and write via toolName
69
+ // discrimination; register the same instance under both connector names so
70
+ // bare-form name-match resolution picks the right route.
68
71
  registry.registerMcpConnector("llm", new LocalModelMcpConnector(openai));
69
- registry.registerMcpConnector("data_read", new DataStoreMcpConnector(dataStore));
72
+ const dataBridge = new DataStoreMcpConnector(dataStore);
73
+ registry.registerMcpConnector("data_read", dataBridge);
74
+ registry.registerMcpConnector("data_write", dataBridge);
70
75
 
71
76
  // Step 4: wire connectors.json instances (adopter-defined MCP servers).
72
77
  for (const c of connectors) {
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Onboarding scaffold connectors.json — wires example MCP servers an adopter might add alongside the bundled bridges. Most onboarding deployments don't need anything here; the file-backed memory + OpenAI LLM + tmux-shell wiring lives in bootstrap.ts. This file is the place for adopter-specific MCP servers (your in-house tools, a YouTrack instance, a custom agent API, etc.).",
2
+ "_comment": "Onboarding scaffold connectors.json — wires example MCP servers an adopter might add alongside the bundled bridges. Most onboarding deployments don't need anything here; the file-backed data store + OpenAI LLM + tmux-shell wiring lives in bootstrap.ts. This file is the place for adopter-specific MCP servers (your in-house tools, a YouTrack instance, a custom agent API, etc.).",
3
3
 
4
4
  "_example_remote_mcp": {
5
5
  "class": "RemoteMcpConnector",
@@ -22,11 +22,11 @@ import type {
22
22
  } from "skillscript-runtime/connectors";
23
23
 
24
24
  export interface FileDataStoreConfig {
25
- /** Absolute path to the JSON file holding the memory array. */
25
+ /** Absolute path to the JSON file holding the data records. */
26
26
  filePath: string;
27
27
  }
28
28
 
29
- interface FileMemoryRecord extends PortableData {
29
+ interface FileDataRecord extends PortableData {
30
30
  /** Optional substrate-specific fields go in `metadata`; everything top-level matches PortableData. */
31
31
  }
32
32
 
@@ -77,7 +77,7 @@ export class FileDataStore implements DataStore {
77
77
  const created_at = Math.floor(Date.now() / 1000);
78
78
  const firstLine = entry.content.split("\n")[0] ?? entry.content;
79
79
  const summary = firstLine.length > 200 ? firstLine.slice(0, 197) + "..." : firstLine;
80
- const newRecord: FileMemoryRecord = {
80
+ const newRecord: FileDataRecord = {
81
81
  id,
82
82
  summary,
83
83
  detail: entry.content,
@@ -92,7 +92,7 @@ export class FileDataStore implements DataStore {
92
92
  },
93
93
  }
94
94
  : {}),
95
- } as FileMemoryRecord;
95
+ } as FileDataRecord;
96
96
  records.push(newRecord);
97
97
  writeFileSync(this.config.filePath, JSON.stringify(records, null, 2), "utf8");
98
98
  return { id, created_at };
@@ -106,19 +106,30 @@ export class FileDataStore implements DataStore {
106
106
  file_path: this.config.filePath,
107
107
  record_count: this.loadFile().length,
108
108
  supports_write: true,
109
+ // v0.14.1 strict-filters: the bridge enforces every non-base filter
110
+ // key in `query()` calls against this declared set, throwing
111
+ // UnsupportedFilterError for unknowns. This reference impl's
112
+ // substring scorer doesn't actually filter on any field (it
113
+ // ignores everything beyond `query` + `limit`), so the honest
114
+ // declaration is `[]`. Adopters who extend `query()` to honor
115
+ // `domain_tags` / `payload_type` / etc. should add those names
116
+ // here so the bridge stops rejecting them. Callers that need
117
+ // to pass advisory filters this impl ignores can opt out per-
118
+ // call via `permissive_filters: true`.
119
+ supported_filters: [],
109
120
  },
110
121
  };
111
122
  }
112
123
 
113
- private loadFile(): FileMemoryRecord[] {
124
+ private loadFile(): FileDataRecord[] {
114
125
  if (!existsSync(this.config.filePath)) return [];
115
126
  try {
116
127
  const raw = readFileSync(this.config.filePath, "utf8");
117
128
  const parsed: unknown = JSON.parse(raw);
118
129
  if (!Array.isArray(parsed)) {
119
- throw new Error(`FileDataStore: '${this.config.filePath}' top-level must be an array of memory records.`);
130
+ throw new Error(`FileDataStore: '${this.config.filePath}' top-level must be an array of data records.`);
120
131
  }
121
- return parsed as FileMemoryRecord[];
132
+ return parsed as FileDataRecord[];
122
133
  } catch (err) {
123
134
  throw new Error(`FileDataStore: failed to read '${this.config.filePath}': ${(err as Error).message}`);
124
135
  }
@@ -34,7 +34,7 @@ const SKILL_SRC = `# Skill: heartbeat
34
34
  # Triggers: cron: */1 * * * *
35
35
 
36
36
  emit:
37
- ! heartbeat at $(EVENT.fired_at_unix)
37
+ emit(text="heartbeat at $(EVENT.fired_at_unix)")
38
38
 
39
39
  default: emit
40
40
  `;
@@ -1,30 +1,31 @@
1
1
  # Skill: classify-support-ticket
2
- # Status: Approved v1:99819a42
2
+ # Status: Approved v1:670a94cf
3
+ # Autonomous: true
3
4
  # Description: Read an incoming support ticket and route it: severity-1 tickets get paged to ops-channel, billing tickets get tagged for finance review, everything else gets a draft reply queued for human review
4
5
  # Vars: TICKET_TEXT, TICKET_ID
5
6
  # Output: agent: ops-oncall
6
7
 
7
8
  classify:
8
- $ llm prompt="Categorize this support ticket. Reply with EXACTLY one of: 'sev-1', 'billing', 'general'. No other text.\n\nTicket: ${TICKET_TEXT}" model=qwen maxTokens=10 -> CATEGORY
9
+ $ llm prompt="Categorize this support ticket. Reply with EXACTLY one of: 'sev-1', 'billing', 'general'. No other text.\n\nTicket: ${TICKET_TEXT}" maxTokens=10 -> CATEGORY
9
10
 
10
11
  severity_check:
11
12
  needs: classify
12
- $ llm prompt="Does this support ticket describe a service outage, data loss, security incident, or other production-severity-1 issue? Reply ONLY 'yes' or 'no'.\n\nTicket: ${TICKET_TEXT}" model=qwen maxTokens=10 -> IS_SEV1
13
+ $ llm prompt="Does this support ticket describe a service outage, data loss, security incident, or other production-severity-1 issue? Reply ONLY 'yes' or 'no'.\n\nTicket: ${TICKET_TEXT}" maxTokens=10 -> IS_SEV1
13
14
 
14
15
  route:
15
16
  needs: severity_check
16
17
  if ${CATEGORY|trim} == "sev-1":
17
18
  emit(text="PAGE: sev-1 ticket ${TICKET_ID} - ${TICKET_TEXT}")
18
- $ datastore.write summary="sev-1 ticket ${TICKET_ID}" detail="${TICKET_TEXT}" knowledge_type=hard_won vault=private domain_tags=["support","sev-1","page"]
19
+ $ data_write content="sev-1 ticket ${TICKET_ID}: ${TICKET_TEXT}" tags=["support","sev-1","page"]
19
20
  elif ${IS_SEV1|trim} == "yes":
20
21
  emit(text="PAGE: classifier said ${CATEGORY|trim} but severity-check flagged this as sev-1: ${TICKET_ID}")
21
- $ datastore.write summary="sev-1 escalation ${TICKET_ID}" detail="category=${CATEGORY|trim} but severity-check=yes" knowledge_type=hard_won vault=private domain_tags=["support","sev-1","disagreement"]
22
+ $ data_write content="sev-1 escalation ${TICKET_ID}: category=${CATEGORY|trim} but severity-check=yes" tags=["support","sev-1","disagreement"]
22
23
  elif ${CATEGORY|trim} == "billing":
23
24
  $set TAG_FOR = "finance"
24
25
  emit(text="Tagged for ${TAG_FOR} review: ${TICKET_ID}")
25
- $ datastore.write summary="billing ticket ${TICKET_ID}" detail="${TICKET_TEXT}" knowledge_type=common vault=private domain_tags=["support","billing"]
26
+ $ data_write content="billing ticket ${TICKET_ID}: ${TICKET_TEXT}" tags=["support","billing"]
26
27
  else:
27
- $ llm prompt="Draft a polite acknowledgment reply for this support ticket. Two short sentences. No greeting, no sign-off.\n\n${TICKET_TEXT}" model=qwen maxTokens=150 -> DRAFT
28
- $ datastore.write summary="support draft ${TICKET_ID}" detail="${DRAFT|trim}" knowledge_type=common vault=private domain_tags=["support","draft"]
28
+ $ llm prompt="Draft a polite acknowledgment reply for this support ticket. Two short sentences. No greeting, no sign-off.\n\n${TICKET_TEXT}" maxTokens=150 -> DRAFT
29
+ $ data_write content="support draft ${TICKET_ID}: ${DRAFT|trim}" tags=["support","draft"]
29
30
 
30
31
  default: route
@@ -1,5 +1,5 @@
1
1
  # Skill: cut-release-tag
2
- # Status: Approved v1:eb29a798
2
+ # Status: Approved v1:d3c2a91b
3
3
  # Description: Run when the user says "ship a release" or "cut a tag" on the current branch; collects commit-list since last tag, asks the user to confirm the version bump, applies the tag, and pushes it
4
4
  # Vars: BUMP_KIND=patch
5
5
  # Output: text
@@ -12,13 +12,13 @@ else:
12
12
 
13
13
  commits_since:
14
14
  needs: last_tag
15
- shell(command="git log --oneline ${PREV_TAG}..HEAD") -> COMMITS
15
+ shell(command="git log --oneline \"${PREV_TAG}\"..HEAD") -> COMMITS
16
16
  else:
17
17
  $set COMMITS = "(unable to read commit log; proceeding without preview)"
18
18
 
19
19
  propose:
20
20
  needs: commits_since
21
- $ llm prompt="Propose the next semver tag given prev tag '${PREV_TAG}', bump kind '${BUMP_KIND}', and commits below. Return ONLY the new tag string (e.g. v1.4.3). No commentary.\n\nCommits:\n${COMMITS}" model=qwen maxTokens=30 -> PROPOSED_TAG
21
+ $ llm prompt="Propose the next semver tag given prev tag '${PREV_TAG}', bump kind '${BUMP_KIND}', and commits below. Return ONLY the new tag string (e.g. v1.4.3). No commentary.\n\nCommits:\n${COMMITS}" maxTokens=30 -> PROPOSED_TAG
22
22
 
23
23
  confirm:
24
24
  needs: propose
@@ -30,11 +30,11 @@ confirm:
30
30
 
31
31
  apply:
32
32
  needs: confirm
33
- shell(command="git tag ${PROPOSED_TAG|trim}") -> TAG_OUT
34
- shell(command="git push origin ${PROPOSED_TAG|trim}") -> PUSH_OUT
33
+ shell(command="git tag \"${PROPOSED_TAG|trim}\"") -> TAG_OUT
34
+ shell(command="git push origin \"${PROPOSED_TAG|trim}\"") -> PUSH_OUT
35
35
  emit(text="Tagged and pushed ${PROPOSED_TAG|trim}")
36
36
  else:
37
37
  emit(text="Tag/push failed; rolling back local tag")
38
- shell(command="git tag -d ${PROPOSED_TAG|trim}") -> ROLLBACK
38
+ shell(command="git tag -d \"${PROPOSED_TAG|trim}\"") -> ROLLBACK
39
39
 
40
40
  default: apply
@@ -1,12 +1,12 @@
1
1
  # Skill: doc-qa-with-citations
2
2
  # Status: Approved v1:2187bf35
3
- # Description: When the user asks a question that requires retrieval over the doc set, answer with inline citations to memory IDs
3
+ # Description: When the user asks a question that requires retrieval over the doc set, answer with inline citations to record IDs
4
4
  # Vars: QUESTION, K=6
5
5
  # Output: text
6
6
 
7
7
  answer:
8
8
  $ data_read mode=rerank query="${QUESTION}" limit=${K} -> HITS (fallback: [])
9
- $ llm prompt="Answer the question using ONLY the supplied passages. Cite each claim inline as [id:<memory-id>]. Question: ${QUESTION}. Passages: ${HITS|json}" maxTokens=900 -> RESPONSE
9
+ $ llm prompt="Answer the question using ONLY the supplied passages. Cite each claim inline as [id:<record-id>]. Question: ${QUESTION}. Passages: ${HITS|json}" maxTokens=900 -> RESPONSE
10
10
  emit(text="${RESPONSE}")
11
11
 
12
12
  default: answer
@@ -1,12 +1,13 @@
1
1
  # Skill: feedback-sentiment-scan
2
- # Status: Approved v1:111610fc
2
+ # Status: Approved v1:4484f7e0
3
+ # Autonomous: true
3
4
  # Description: Each night, scan the previous 24h of customer feedback records, classify sentiment via local model, surface entries where sentiment is "frustrated" or "blocking" so the team sees them at start-of-day; skip entries already seen on prior nights
4
5
  # Triggers: cron: 0 3 * * *
5
6
  # Vars: SCAN_LIMIT=50
6
7
  # Output: agent: support-lead
7
8
 
8
9
  fetch_new:
9
- $ data_read mode=fts query="customer feedback" limit=${SCAN_LIMIT} created_after=${EVENT.fired_at_unix} -> FEEDBACK
10
+ $ data_read mode=fts query="customer feedback" limit=${SCAN_LIMIT} -> FEEDBACK
10
11
 
11
12
  fetch_seen:
12
13
  $ data_read mode=fts query="sentiment-scan seen marker" limit=200 domain_tags=["sentiment-scan-seen"] -> SEEN_MARKERS
@@ -14,15 +15,15 @@ fetch_seen:
14
15
  classify_and_emit:
15
16
  needs: fetch_new, fetch_seen
16
17
  emit(text="Sentiment scan results for ${NOW}:")
17
- foreach F in ${FEEDBACK}:
18
- if ${F.id|trim} in ${SEEN_MARKERS}:
18
+ foreach F in ${FEEDBACK.items}:
19
+ if ${F.id|trim} in ${SEEN_MARKERS.items}:
19
20
  emit(text="- skipped (already classified): ${F.id|trim}")
20
- elif ${F.id|trim} not in ${SEEN_MARKERS}:
21
- $ llm prompt="Classify the sentiment of this customer feedback. Respond with ONE word: 'frustrated', 'blocking', 'satisfied', 'neutral'. No explanation.\n\nFeedback: ${F.summary}\nDetail: ${F.detail}" model=gemma2 maxTokens=10 -> VERDICT
21
+ elif ${F.id|trim} not in ${SEEN_MARKERS.items}:
22
+ $ llm prompt="Classify the sentiment of this customer feedback. Respond with ONE word: 'frustrated', 'blocking', 'satisfied', 'neutral'. No explanation.\n\nFeedback: ${F.summary}\nDetail: ${F.detail}" maxTokens=10 -> VERDICT
22
23
  if ${VERDICT|trim} == "frustrated":
23
24
  emit(text="- FRUSTRATED [${F.id|trim}] ${F.summary}")
24
25
  elif ${VERDICT|trim} == "blocking":
25
26
  emit(text="- BLOCKING [${F.id|trim}] ${F.summary}")
26
- $ datastore.write summary="sentiment-scan seen ${F.id|trim}" detail="verdict=${VERDICT|trim} on ${EVENT.fired_at_unix}" knowledge_type=common vault=private domain_tags=["sentiment-scan-seen"] expires_at=${EVENT.fired_at_plus_7d_unix}
27
+ $ data_write content="sentiment-scan seen ${F.id|trim} verdict=${VERDICT|trim} on ${EVENT.fired_at_unix}" tags=["sentiment-scan-seen"] expires_at=${EVENT.fired_at_plus_7d_unix} -> ACK
27
28
 
28
29
  default: classify_and_emit
@@ -2,7 +2,7 @@
2
2
  "provenance_version": "1.0",
3
3
  "language_version": "1.0",
4
4
  "compiler_version": "0.1.0-dev",
5
- "compiled_at": "2026-05-30T14:28:14.260Z",
5
+ "compiled_at": "2026-05-31T19:21:57.734Z",
6
6
  "source_skill": {
7
7
  "name": "hello"
8
8
  },
@@ -1,6 +1,7 @@
1
1
  # Skill: service-health-watch
2
- # Status: Approved v1:7c93b4b4
3
- # Description: Every 5 minutes check named service endpoints — if latency or status degrades, write a signal memory and alert
2
+ # Status: Approved v1:628c8c69
3
+ # Autonomous: true
4
+ # Description: Every 5 minutes check named service endpoints — if latency or status degrades, write a signal record and alert
4
5
  # Vars: SERVICES=[auth-api, ledger-api, search-api], LATENCY_BUDGET_MS=400
5
6
  # Triggers: cron: */5 * * * *
6
7
  # Output: none
@@ -10,8 +11,8 @@ probe:
10
11
  shell(command="curl -s -o /dev/null -w \"%{http_code} %{time_total}\" https://status.internal/${SVC|url}") -> RAW
11
12
  $ llm prompt="From the line '${RAW|trim}' (http_code time_seconds), and budget ${LATENCY_BUDGET_MS} ms, answer ok or degraded only." -> STATUS
12
13
  if ${STATUS|trim} == "degraded":
13
- $ datastore.write summary="service degradation: ${SVC}" detail="probe at ${NOW}: ${RAW|trim}" domain_tags=[ops, service-health, degraded:${SVC}] vault=private knowledge_type=common expires_at=${EVENT.fired_at_plus_1d_unix} -> ACK
14
- emit(text="${SVC} degraded — wrote signal")
14
+ $ data_write content="service degradation: ${SVC} at ${NOW}: ${RAW|trim}" tags=["ops","service-health","degraded:${SVC}"] expires_at=${EVENT.fired_at_plus_1d_unix} -> ACK
15
+ emit(text="${SVC} degraded — wrote signal ${ACK.id}")
15
16
  else:
16
17
  emit(text="${SVC} ok")
17
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscript-runtime",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Scott Shwarts <scotts@pobox.com>",
@@ -26,6 +26,8 @@ resolution_order = ["filesystem", "packages"]
26
26
  # See docs/configuration.md.
27
27
 
28
28
  [runtime]
29
- # `@@` opt-in unsafe shell. Disabled by default. v1 spec gates this behind
30
- # explicit opt-in plus a tier-2 lint warning.
29
+ # `shell(command="...", unsafe=true)` (legacy `@ unsafe <body>`) opt-in
30
+ # full-bash shell. Disabled by default. Spec gates this behind explicit
31
+ # opt-in plus a tier-2 lint warning that fires every appearance. Set to
32
+ # `true` only after auditing every unsafe-shell op in your skill corpus.
31
33
  enable_unsafe_shell = false