@almadar/std 16.247.0 → 16.248.0

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.
@@ -0,0 +1,336 @@
1
+ app std-learn-generate [
2
+ version: "1.0.0",
3
+ description: "Generic instrument generator — GENERATE { subject, context } picks one learning instrument for a subject and writes its roster. It lists the installed behaviors under `scope` (optionally narrowed to the `instruments` allow-list), keeps those whose trait listens for ROSTER_LOADED (an instrument that takes its roster at runtime), asks the model to CHOOSE one, then to FILL a roster whose fields are exactly that instrument's ROSTER_LOADED payload; the tool loop checks the roster against those declared fields before it is accepted. GENERATED { behavior, mode, roster } carries the instrument, its mode (the behavior's name, the matchMode a lab router uses) and the roster as JSON, ready to send as that instrument's ROSTER_LOADED. It never writes a program: when no instrument in scope fits or the roster cannot be filled, it emits GENERATE_FAILED { error }. The instrument list is read once and kept for later requests."
4
+ ]
5
+
6
+ ;; @capabilities "pick a learning instrument for a subject, write an instrument's roster, generate an interactive visualisation for a concept"
7
+
8
+ type CatalogHit = {
9
+ behavior : string!
10
+ description : string
11
+ traits : [trait]!
12
+ vectors : Map string [number]
13
+ }
14
+ type AgentChatMessage = {
15
+ role : "user" | "assistant"
16
+ content : string!
17
+ }
18
+ type AgentToolField = {
19
+ name : string!
20
+ type : string!
21
+ required : boolean
22
+ values : [string]
23
+ properties : [AgentToolField]
24
+ description : string
25
+ }
26
+ type AgentToolInput = {
27
+ event : EventAddress!
28
+ payload : [AgentToolField]
29
+ }
30
+ type LearnInstrument = {
31
+ behavior : string!
32
+ description : string
33
+ fields : [AgentToolField]!
34
+ }
35
+
36
+ uses ToolLoop from "std/behaviors/std-agent-tool-loop"
37
+
38
+ orbital LearnGenerateOrbital {
39
+
40
+ entity LearnGeneration [runtime] {
41
+ id : string!
42
+ subject : string = ""
43
+ context : string = ""
44
+ candidates : [CatalogHit] = []
45
+ instruments : [LearnInstrument] = []
46
+ cursor : number = 0
47
+ chosen : string = ""
48
+ roster : string = ""
49
+ error : string = ""
50
+ status : "idle" | "listing" | "sourcing" | "choosing" | "filling" | "done" | "failed" = idle
51
+ }
52
+
53
+ trait LearnGenerate -> LearnGeneration [lifecycle, instance] {
54
+ config {
55
+ scope : [string] = ["almadar-std/ui/learning"]
56
+ @label "Behavior folders"
57
+ @description "Registry folders (`<prefix>/<topic>[/<tier>]`) the generator may draw instruments from; nothing outside them is ever offered"
58
+ @synonyms "scope, catalog, folders"
59
+ @tier "domain"
60
+ instruments : [string] = []
61
+ @label "Allowed instruments"
62
+ @description "Behavior names (`almadar-std/std-learn-predict`) to choose among; empty offers every instrument in the folders"
63
+ @synonyms "allow list, instrument list"
64
+ @tier "domain"
65
+ chooseEvent : EventAddress = LearnGenerate.CHOOSE
66
+ @label "Choose tool event"
67
+ @description "The address the model's CHOOSE tool fires; a consumer that renames this trait sets it to its own name (`VizGen.CHOOSE`)"
68
+ @tier "internal"
69
+ fillEvent : EventAddress = LearnGenerate.FILL
70
+ @label "Fill tool event"
71
+ @description "The address the model's FILL tool fires; a consumer that renames this trait sets it to its own name (`VizGen.FILL`)"
72
+ @tier "internal"
73
+ statusSlot : slot = main
74
+ @label "Status slot"
75
+ @description "Where the atom shows its one-line status; a page running it in the background sends it to `system`"
76
+ @synonyms "status area, slot"
77
+ @tier "presentation"
78
+ }
79
+
80
+ state idle {
81
+ INIT -> idle
82
+ (set @entity.status idle)
83
+ CHOOSE -> idle
84
+ FILL -> idle
85
+ GENERATE -> listing when (= (array/len @entity.instruments) 0)
86
+ (set @entity.subject ?subject)
87
+ (set @entity.context (object/get @payload context ""))
88
+ (set @entity.chosen "")
89
+ (set @entity.roster "")
90
+ (set @entity.error "")
91
+ (set @entity.status listing)
92
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "Finding an instrument…" })
93
+ (behavior/catalog { paths: @config.scope } {
94
+ emit: { failure: GENERATE_FAILED, success: CATALOG_LISTED }
95
+ })
96
+ GENERATE -> choosing when (> (array/len @entity.instruments) 0)
97
+ (set @entity.subject ?subject)
98
+ (set @entity.context (object/get @payload context ""))
99
+ (set @entity.chosen "")
100
+ (set @entity.roster "")
101
+ (set @entity.error "")
102
+ (set @entity.status choosing)
103
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "Choosing an instrument…" })
104
+ (emit CONVERSE {
105
+ messages: [{ role: "user", content: (str/concat "Subject: " ?subject (if (= (object/get @payload context "") "") "" (str/concat "\nContext: " (object/get @payload context "")))) }]
106
+ instructions: (str/concat "You pick the one interactive learning instrument that best lets a learner explore the subject. The installed instruments:\n" (str/join (array/map @entity.instruments (fn i (str/concat "- " (object/get @i behavior) ": " (object/get @i description "")))) "\n") "\nCall the CHOOSE tool once with exactly one of them, then reply with one sentence.")
107
+ tools: [{
108
+ event: @config.chooseEvent
109
+ payload: [{ name: "choice", type: "string", required: true, values: (array/map @entity.instruments (fn i (object/get @i behavior))) }]
110
+ }]
111
+ ephemeral: true
112
+ })
113
+ }
114
+
115
+ state listing {
116
+ INIT -> listing
117
+ (set @entity.status listing)
118
+ CATALOG_LISTED -> sourcing when (> (array/len (array/filter ?result (fn h (or (= (array/len @config.instruments) 0) (array/includes @config.instruments (object/get @h behavior)))))) 0)
119
+ (let ((hits (array/filter ?result (fn h (or (= (array/len @config.instruments) 0) (array/includes @config.instruments (object/get @h behavior)))))))
120
+ (do
121
+ (set @entity.candidates @hits)
122
+ (set @entity.cursor 0)
123
+ (set @entity.status sourcing)
124
+ (behavior/source { behavior: (object/get (array/first @hits) behavior) } {
125
+ emit: { failure: GENERATE_FAILED, success: SOURCED }
126
+ })))
127
+ CATALOG_LISTED -> failed when (= (array/len (array/filter ?result (fn h (or (= (array/len @config.instruments) 0) (array/includes @config.instruments (object/get @h behavior)))))) 0)
128
+ (set @entity.error "No behavior in scope to choose from")
129
+ (set @entity.status failed)
130
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
131
+ (emit GENERATE_FAILED { error: "No behavior in scope to choose from" })
132
+ GENERATE_FAILED -> failed
133
+ (set @entity.error ?error)
134
+ (set @entity.status failed)
135
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
136
+ (emit GENERATION_FAILED { error: ?error })
137
+ }
138
+
139
+ state sourcing {
140
+ INIT -> sourcing
141
+ (set @entity.status sourcing)
142
+ SOURCED -> sourcing when (< (+ @entity.cursor 1) (array/len @entity.candidates))
143
+ (let ((fields (array/flatten (array/map (object/get ?result orbitals []) (fn o (array/flatten (array/map (object/get @o traits []) (fn t (array/flatten (array/map (array/filter (object/get (object/get @t stateMachine {}) events []) (fn e (= (object/get @e key "") "ROSTER_LOADED"))) (fn e (array/filter (object/get @e payloadSchema []) (fn f (!= (object/get @f name "") "mode"))))))))))))))
144
+ (do
145
+ (set @entity.instruments (if (> (array/len @fields) 0) (array/append @entity.instruments { behavior: (object/get (array/nth @entity.candidates @entity.cursor) behavior), description: (object/get (array/nth @entity.candidates @entity.cursor) description ""), fields: @fields }) @entity.instruments))
146
+ (set @entity.cursor (+ @entity.cursor 1))
147
+ (behavior/source { behavior: (object/get (array/nth @entity.candidates (+ @entity.cursor 1)) behavior) } {
148
+ emit: { failure: GENERATE_FAILED, success: SOURCED }
149
+ })))
150
+ SOURCED -> choosing when (>= (+ @entity.cursor 1) (array/len @entity.candidates))
151
+ (let ((fields (array/flatten (array/map (object/get ?result orbitals []) (fn o (array/flatten (array/map (object/get @o traits []) (fn t (array/flatten (array/map (array/filter (object/get (object/get @t stateMachine {}) events []) (fn e (= (object/get @e key "") "ROSTER_LOADED"))) (fn e (array/filter (object/get @e payloadSchema []) (fn f (!= (object/get @f name "") "mode"))))))))))))))
152
+ (do
153
+ (set @entity.instruments (if (> (array/len @fields) 0) (array/append @entity.instruments { behavior: (object/get (array/nth @entity.candidates @entity.cursor) behavior), description: (object/get (array/nth @entity.candidates @entity.cursor) description ""), fields: @fields }) @entity.instruments))
154
+ (set @entity.status choosing)
155
+ (emit INSTRUMENTS_READY {})))
156
+ GENERATE_FAILED -> failed
157
+ (set @entity.error ?error)
158
+ (set @entity.status failed)
159
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
160
+ (emit GENERATION_FAILED { error: ?error })
161
+ }
162
+
163
+ state choosing {
164
+ INIT -> choosing
165
+ (set @entity.status choosing)
166
+ INSTRUMENTS_READY -> choosing when (> (array/len @entity.instruments) 0)
167
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "Choosing an instrument…" })
168
+ (emit CONVERSE {
169
+ messages: [{ role: "user", content: (str/concat "Subject: " @entity.subject (if (= @entity.context "") "" (str/concat "\nContext: " @entity.context))) }]
170
+ instructions: (str/concat "You pick the one interactive learning instrument that best lets a learner explore the subject. The installed instruments:\n" (str/join (array/map @entity.instruments (fn i (str/concat "- " (object/get @i behavior) ": " (object/get @i description "")))) "\n") "\nCall the CHOOSE tool once with exactly one of them, then reply with one sentence.")
171
+ tools: [{
172
+ event: @config.chooseEvent
173
+ payload: [{ name: "choice", type: "string", required: true, values: (array/map @entity.instruments (fn i (object/get @i behavior))) }]
174
+ }]
175
+ ephemeral: true
176
+ })
177
+ INSTRUMENTS_READY -> failed when (= (array/len @entity.instruments) 0)
178
+ (set @entity.error "No instrument in scope takes a roster")
179
+ (set @entity.status failed)
180
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
181
+ (emit GENERATION_FAILED { error: "No instrument in scope takes a roster" })
182
+ CHOOSE -> choosing
183
+ (set @entity.chosen ?choice)
184
+ ASSISTANT_REPLIED -> filling when (array/some @entity.instruments (fn i (= (object/get @i behavior) @entity.chosen)))
185
+ (set @entity.status filling)
186
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "Writing the roster…" })
187
+ (emit CONVERSE {
188
+ messages: [{ role: "user", content: (str/concat "Subject: " @entity.subject (if (= @entity.context "") "" (str/concat "\nContext: " @entity.context))) }]
189
+ instructions: (str/concat "You write the content roster for the learning instrument " @entity.chosen ": " (object/get (array/find @entity.instruments (fn i (= (object/get @i behavior) @entity.chosen))) description "") "\nCall the FILL tool once with a roster of realistic, accurate, pedagogically useful content about the subject. If the tool reports issues, call it again with them fixed. Then reply with one sentence.")
190
+ tools: [{
191
+ event: @config.fillEvent
192
+ payload: [{ name: "roster", type: "object", required: true, properties: (object/get (array/find @entity.instruments (fn i (= (object/get @i behavior) @entity.chosen))) fields []) }]
193
+ }]
194
+ ephemeral: true
195
+ })
196
+ ASSISTANT_REPLIED -> failed when (not (array/some @entity.instruments (fn i (= (object/get @i behavior) @entity.chosen))))
197
+ (set @entity.error "The model chose no installed instrument")
198
+ (set @entity.status failed)
199
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
200
+ (emit GENERATION_FAILED { error: "The model chose no installed instrument" })
201
+ TOOLS_FAILED -> failed
202
+ (set @entity.error ?error)
203
+ (set @entity.status failed)
204
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
205
+ (emit GENERATION_FAILED { error: ?error })
206
+ }
207
+
208
+ state filling {
209
+ INIT -> filling
210
+ (set @entity.status filling)
211
+ FILL -> filling
212
+ (set @entity.roster (json/stringify ?roster))
213
+ ASSISTANT_REPLIED -> done when (!= @entity.roster "")
214
+ (set @entity.status done)
215
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "Instrument ready" })
216
+ (emit GENERATED { behavior: @entity.chosen, mode: (array/last (str/split @entity.chosen "/")), roster: @entity.roster })
217
+ ASSISTANT_REPLIED -> failed when (= @entity.roster "")
218
+ (set @entity.error "The model wrote no roster that matches the instrument")
219
+ (set @entity.status failed)
220
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
221
+ (emit GENERATION_FAILED { error: "The model wrote no roster that matches the instrument" })
222
+ TOOLS_FAILED -> failed
223
+ (set @entity.error ?error)
224
+ (set @entity.status failed)
225
+ (render-ui @config.statusSlot { type: typography, variant: caption, role: status, content: "No instrument could be generated" })
226
+ (emit GENERATION_FAILED { error: ?error })
227
+ }
228
+
229
+ state done {
230
+ INIT -> done
231
+ (set @entity.status done)
232
+ GENERATE -> listing when (= (array/len @entity.instruments) 0)
233
+ (set @entity.subject ?subject)
234
+ (set @entity.context (object/get @payload context ""))
235
+ (set @entity.chosen "")
236
+ (set @entity.roster "")
237
+ (set @entity.status listing)
238
+ (behavior/catalog { paths: @config.scope } {
239
+ emit: { failure: GENERATE_FAILED, success: CATALOG_LISTED }
240
+ })
241
+ GENERATE -> choosing when (> (array/len @entity.instruments) 0)
242
+ (set @entity.subject ?subject)
243
+ (set @entity.context (object/get @payload context ""))
244
+ (set @entity.chosen "")
245
+ (set @entity.roster "")
246
+ (set @entity.status choosing)
247
+ (emit INSTRUMENTS_READY {})
248
+ }
249
+
250
+ state failed {
251
+ INIT -> failed
252
+ (set @entity.status failed)
253
+ GENERATE -> listing when (= (array/len @entity.instruments) 0)
254
+ (set @entity.subject ?subject)
255
+ (set @entity.context (object/get @payload context ""))
256
+ (set @entity.chosen "")
257
+ (set @entity.roster "")
258
+ (set @entity.error "")
259
+ (set @entity.status listing)
260
+ (behavior/catalog { paths: @config.scope } {
261
+ emit: { failure: GENERATE_FAILED, success: CATALOG_LISTED }
262
+ })
263
+ GENERATE -> choosing when (> (array/len @entity.instruments) 0)
264
+ (set @entity.subject ?subject)
265
+ (set @entity.context (object/get @payload context ""))
266
+ (set @entity.chosen "")
267
+ (set @entity.roster "")
268
+ (set @entity.error "")
269
+ (set @entity.status choosing)
270
+ (emit INSTRUMENTS_READY {})
271
+ }
272
+
273
+ emits {
274
+ GENERATED -> external {
275
+ behavior : string!
276
+ mode : string!
277
+ roster : string!
278
+ }
279
+ @description "The chosen instrument, its mode (the behavior's name) and its roster as JSON, ready to send as that instrument's ROSTER_LOADED"
280
+ GENERATION_FAILED -> external {
281
+ error : string!
282
+ }
283
+ @description "No instrument could be generated for the subject; nothing is shown"
284
+ CONVERSE {
285
+ messages : [AgentChatMessage]!
286
+ instructions : string
287
+ tools : [AgentToolInput]
288
+ ephemeral : boolean
289
+ }
290
+ CATALOG_LISTED {
291
+ result : [CatalogHit]!
292
+ }
293
+ SOURCED {
294
+ result : SExpr!
295
+ }
296
+ INSTRUMENTS_READY {
297
+ }
298
+ GENERATE_FAILED {
299
+ error : string!
300
+ }
301
+ }
302
+
303
+ listens {
304
+ GENERATE -> external {
305
+ subject : string!
306
+ context : string
307
+ }
308
+ @description "Generate an instrument and roster for this subject; `context` is any extra the model should know (the lesson's wording, what to visualise)"
309
+ @synonyms "visualise, make an instrument, generate"
310
+ CHOOSE -> external {
311
+ choice : string!
312
+ }
313
+ FILL -> external {
314
+ roster : SExpr!
315
+ }
316
+ LearnGenerateTools.ASSISTANT_REPLIED -> ASSISTANT_REPLIED
317
+ LearnGenerateTools.ASSISTANT_REPLY_FAILED -> TOOLS_FAILED
318
+ TOOLS_FAILED {
319
+ error : string!
320
+ }
321
+ }
322
+ }
323
+
324
+ trait LearnGenerateTools = ToolLoop.traits.AgentToolLoop {
325
+ config {
326
+ quiet: true
327
+ maxSteps: 4
328
+ cancelKey: "std-learn-generate"
329
+ }
330
+ listens {
331
+ LearnGenerate.CONVERSE -> CONVERSE
332
+ }
333
+ }
334
+
335
+ page "/learn-generate" as LearnGeneratePage -> LearnGenerate, LearnGenerateTools
336
+ }