@matamt-dbg/dsh-studio 0.4.0 → 0.6.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.
@@ -0,0 +1,364 @@
1
+ # Pixie Loop guide
2
+
3
+ English | [한국어](pixie-loop.ko.md)
4
+
5
+ Pixie Loop repeats **work → judge → fix and retry** outside the main agent's conversation when many items are judged by the same criteria. The agent passes one loop spec to the `pixie_loop` tool and gets back counts, a results file, and only the items that never passed. The attempts and judgments per item stay out of the conversation; see them in **Pixie tab → Loops**.
6
+
7
+ - [When to use it](#when-to-use-it)
8
+ - [Setup](#setup)
9
+ - [How it works](#how-it-works)
10
+ - [Spec fields](#spec-fields)
11
+ - [Writing criteria](#writing-criteria)
12
+ - [Calibration examples](#calibration-examples)
13
+ - [Code checks](#code-checks)
14
+ - [Limits and results](#limits-and-results)
15
+ - [Example specs](#example-specs)
16
+ - [Run and judge (act)](#run-and-judge-act)
17
+ - [Episodes and selection](#episodes-and-selection)
18
+ - [Grouping results (route)](#grouping-results-route)
19
+ - [Recipes](#recipes)
20
+ - [Viewing runs](#viewing-runs)
21
+ - [What it cannot do](#what-it-cannot-do)
22
+ - [Agent skill](#agent-skill)
23
+
24
+ ## When to use it
25
+
26
+ | Situation | Use |
27
+ | --- | --- |
28
+ | Many independent items, all judged by the same criteria (translation review, copy edits, per-file audits) | `pixie_loop` |
29
+ | One judgment is enough | the `judge` tool |
30
+ | The work needs finding and reading files and several steps of judgment | `subagent_role` |
31
+ | Branching flows or many agents working together | the DSH `workflow` tool |
32
+ | Automatic checks at a set moment of the agent's work | [Pixie Gate](pixie-gate.md) |
33
+
34
+ ## Setup
35
+
36
+ - Set the Pixie model in **Studio → Pixie → Settings**. It does the work, compiles criteria, and writes feedback.
37
+ - Choose the decision engine (LLM or TypeSafe Jev) in **Studio → Pixie → Gates → Decision engine**. Jev needs an API key.
38
+ - The preset needs the `pixie_loop` tool. Studio Master includes it.
39
+
40
+ Item contents are sent to the Pixie model provider and to the decision engine (TypeSafe, when Jev is chosen).
41
+
42
+ ## How it works
43
+
44
+ 1. **Prepare checks (once):** Pixie turns `criteria` into decision-engine questions and pass rules. Giving `checks` skips this step.
45
+ 2. **Calibrate (once):** every pass example must pass and every fail example must fail. Otherwise the loop does not start and returns an error. When calibration passes, the loop starts without asking.
46
+ 3. **Per item:**
47
+ - Pixie produces an output from `work.instruction` and `shared`.
48
+ - Code checks (`codeChecks`) run first. If any fails, the decision engine is not called.
49
+ - The decision engine judges with the fixed questions; pass rules decide.
50
+ - On failure Pixie writes what to change, and the same item is tried again.
51
+ 4. **End:** the results file is written and the summary returned.
52
+
53
+ Context rules:
54
+
55
+ - Items never see each other or the main conversation.
56
+ - An item's retries accumulate that item's previous outputs and feedback.
57
+ - Compiling checks and writing feedback each start from a fresh conversation.
58
+
59
+ Only code checks and the decision engine decide pass or fail. Pixie's feedback guides the next attempt and never changes a verdict. The decision engine gives no reasons, so the reasons in the feedback are Pixie's guess.
60
+
61
+ ## Spec fields
62
+
63
+ | Field | Required | Description |
64
+ | --- | --- | --- |
65
+ | `items` | yes | `{ "inline": [{ "id", "input" }] }` or `{ "path": "work/items.jsonl" }` with one `{"id","input"}` per line. Ids are unique. Up to 2000 items, 32,000 characters per input |
66
+ | `work.instruction` | yes, without recipe | What to do with one item. The worker has no tools |
67
+ | `shared` | | Fixed material every item sees (glossary, style guide). Up to 64,000 characters |
68
+ | `criteria` | one of | Review criteria in plain words. Only a plain pick-once `select` may omit both |
69
+ | `checks` | one of | Questions and pass rules given directly; see [below](#giving-checks-directly) |
70
+ | `codeChecks` | | Exact checks run before the decision engine; see [Code checks](#code-checks) |
71
+ | `calibration` | yes, with criteria or checks | `{ "pass": [{ "input", "output", "observation"? }], "fail": [...] }`, 1–10 each |
72
+ | `mode` | | `retry` (default) or `episode`; see [episodes](#episodes-and-selection) |
73
+ | `act` | | Run each output and judge what it printed; see [act](#run-and-judge-act) |
74
+ | `extract` | | Shorten observations before judging |
75
+ | `select` | | The decision engine picks one listed option |
76
+ | `route` | | Group results by a choice answer; see [route](#grouping-results-route) |
77
+ | `maxAttempts` | | Tries per item (at most 10) or steps per episode (at most 100). Default 3 |
78
+ | `concurrency` | | Items in flight. Default 4, at most 8 |
79
+ | `limits` | yes, without recipe | `{ "tokens", "timeMs" }`. Reaching either stops the run; remaining items are reported as skipped |
80
+ | `output` | | Results JSONL path |
81
+ | `recipe` | | A saved [recipe](#recipes) name; with it give only `items`, `output`, and `run_in_background` |
82
+ | `run_in_background` | | `true` returns a job id at once. Collect the summary with `job_output`; stop with `job_kill` |
83
+
84
+ ## Writing criteria
85
+
86
+ The decision engine judges a state holding `input` (the item) and `output` (the worker's result).
87
+
88
+ - **One thing per sentence.** Split "keeps the meaning, is polite, follows the glossary" into three sentences; Pixie makes one question from each.
89
+ - **Name failure types.** "If there is a problem, say whether it is a mistranslation, terminology, tone, or omission" yields a choice question and sharper feedback.
90
+ - **Keep exact checks out of criteria.** Placeholder preservation, JSON shape, and required characters belong in [code checks](#code-checks). The decision engine returns probabilities and is a poor fit for them.
91
+ - The decision engine is most accurate with English questions; English criteria are more stable.
92
+
93
+ ### Giving checks directly
94
+
95
+ ```json
96
+ {
97
+ "questions": {
98
+ "faithful": { "type": "noul", "instructions": "Does `output` keep the full meaning of `input`?" },
99
+ "issue": { "type": "choice", "instructions": "What is the main problem in `output`?",
100
+ "criteria": { "ok": "no problem", "mistranslation": "meaning changed", "terminology": "glossary not followed", "tone": "not polite 해요체" } },
101
+ "natural": { "type": "score", "instructions": "How natural is `output` as Korean UI text?", "criteria": ["awkward", "understandable", "natural"] }
102
+ },
103
+ "pass": { "faithful": { "min": 0.7 }, "issue": { "accept": ["ok"] }, "natural": { "min": 1.5 } }
104
+ }
105
+ ```
106
+
107
+ - `noul`: probability of yes. Passes at `min` (above 0, at most 1) or more. Phrase it so yes is the good answer.
108
+ - `choice`: passes when the choice is in `accept`. At least one option must fail.
109
+ - `score`: level index from 0. Passes at `min` or more.
110
+ - 1–10 questions. Question ids do not reach the engine, so `instructions` must carry the whole meaning.
111
+
112
+ ## Calibration examples
113
+
114
+ Calibration checks that the criteria actually separate good from bad outputs before hundreds of items run on a wrong standard.
115
+
116
+ - **pass:** outputs you would accept. One common form and one borderline-but-fine case work well.
117
+ - **fail:** realistic mistakes, one per criterion (changed meaning, wrong register, missing placeholder).
118
+ - Only obvious examples (empty strings, another language) make the check weak.
119
+
120
+ When the start is refused, the error names each example judged the wrong way and its values. Reword that question or give `checks` directly and run again.
121
+
122
+ ## Code checks
123
+
124
+ | Check | Options | Fails when |
125
+ | --- | --- | --- |
126
+ | `{"type":"placeholders"}` | | Placeholders, tags, inline code spans, and Markdown link targets in the input (`{name}`, `{{count}}`, `%s`/`%1$d`, `<b>`/`</b>`) are not all in the output the same number of times, or new ones appear |
127
+ | `{"type":"regex","pattern":"[가-힣]","expect":"match"}` | `flags`: `i` `m` `s` `u` | `match`: the output does not match; `no-match`: it does |
128
+ | `{"type":"json","keys":["title"]}` | `keys` optional | The output is not JSON, or the object lacks a listed key |
129
+
130
+ A failed code check is passed to the next attempt as is.
131
+
132
+ ## Limits and results
133
+
134
+ - `limits.tokens` counts Pixie and decision-engine input and output tokens. Estimate roughly `items × attempts × (input + output + shared)` and leave headroom.
135
+ - A stopped run has status `stopped` and the remaining items `skipped`. Rerun with only the ids missing from the results file to continue.
136
+ - Each results line: `{"id","status","output"?,"attempts","error"?}` with `status` `passed`, `escalated`, `error`, or `cancelled`.
137
+ - The agent's summary: passed count, `escalated` (items that used every attempt without passing: id, attempts, failed checks, last feedback), `errors` (engine failures and the like), skipped count, results file, usage. Passed outputs are not in the summary.
138
+ - The main agent fixes `escalated` items itself or asks you.
139
+ - A decision-engine failure is never turned into a fail; the item is marked `error`.
140
+
141
+ ## Example specs
142
+
143
+ ### Translation review
144
+
145
+ ```json
146
+ {
147
+ "items": { "path": "work/ko-pending.jsonl" },
148
+ "shared": "Glossary: session → 세션, preset → 프리셋, tool → 도구. UI copy uses polite 해요체 and no trailing period on buttons.",
149
+ "work": { "instruction": "Translate the English UI string to natural Korean. Keep placeholders and tags exactly." },
150
+ "criteria": "The Korean keeps the full meaning of the English. It follows the glossary in shared context. It uses polite 해요체. If there is a problem, say whether it is a mistranslation, terminology, tone, or omission.",
151
+ "codeChecks": [{ "type": "placeholders" }, { "type": "regex", "pattern": "[가-힣]", "expect": "match" }],
152
+ "calibration": {
153
+ "pass": [{ "input": "Save {name}", "output": "{name} 저장" }, { "input": "Open the session", "output": "세션 열기" }],
154
+ "fail": [{ "input": "Do not retry on 4xx", "output": "4xx에서도 재시도" }, { "input": "Open the session", "output": "세션을 열어라" }]
155
+ },
156
+ "maxAttempts": 3, "concurrency": 4,
157
+ "limits": { "tokens": 2000000, "timeMs": 3600000 },
158
+ "output": "work/ko-done.jsonl",
159
+ "run_in_background": true
160
+ }
161
+ ```
162
+
163
+ ### Copy edit
164
+
165
+ ```json
166
+ {
167
+ "items": { "inline": [{ "id": "intro", "input": "Studio let you build preset and apply it to session." }] },
168
+ "work": { "instruction": "Fix grammar and clarity. Keep the meaning, terms, and Markdown. Reply with the corrected paragraph only." },
169
+ "criteria": "The output keeps the meaning of the input. The output has no grammar errors. The output does not add new claims.",
170
+ "calibration": {
171
+ "pass": [{ "input": "It work fine.", "output": "It works fine." }],
172
+ "fail": [{ "input": "It work fine.", "output": "It works perfectly and is the fastest option." }]
173
+ },
174
+ "limits": { "tokens": 200000, "timeMs": 600000 }
175
+ }
176
+ ```
177
+
178
+ ### Per-file audit (item = file content)
179
+
180
+ The agent reads the files into `items` or a JSONL file first; the worker cannot read files.
181
+
182
+ ```json
183
+ {
184
+ "items": { "path": "work/handlers.jsonl" },
185
+ "work": { "instruction": "List every place in this source file where user input reaches a shell command or SQL string without escaping. Reply with JSON: {\"findings\": [{\"line\": number, \"why\": string}]}. Use an empty list when there are none." },
186
+ "criteria": "Every finding in output points to real unescaped user input in input. No real case in input is missing from output.",
187
+ "codeChecks": [{ "type": "json", "keys": ["findings"] }],
188
+ "calibration": {
189
+ "pass": [{ "input": "exec(`ls ${req.query.dir}`)", "output": "{\"findings\":[{\"line\":1,\"why\":\"query param in shell command\"}]}" }],
190
+ "fail": [{ "input": "exec(`ls ${req.query.dir}`)", "output": "{\"findings\":[]}" }]
191
+ },
192
+ "maxAttempts": 2,
193
+ "limits": { "tokens": 1500000, "timeMs": 1800000 },
194
+ "output": "work/audit.jsonl"
195
+ }
196
+ ```
197
+
198
+ ## Run and judge (act)
199
+
200
+ When an output only proves itself by running it (CSS selectors, regular expressions, command options, config snippets), use `act`. The loop runs one command per output with the session's `bash` tool, and the decision engine judges what it printed (`observation`). The worker (Pixie) still has no tools.
201
+
202
+ ```json
203
+ {
204
+ "items": { "inline": [{ "id": "save-button", "input": "Find a CSS selector for the Save button in page.html" }] },
205
+ "work": { "instruction": "Reply with one CSS selector only." },
206
+ "criteria": "The observation shows exactly one element matched and its text is Save.",
207
+ "act": { "tool": "bash", "command": "node probe.mjs page.html {{output}}", "safeToRepeat": true, "isolated": true, "timeoutMs": 30000 },
208
+ "extract": { "instruction": "Keep the match count and the matched element's text." },
209
+ "calibration": {
210
+ "pass": [{ "input": "Find the Save button", "output": "#save", "observation": "matches: 1\ntext: Save" }],
211
+ "fail": [{ "input": "Find the Save button", "output": "button", "observation": "matches: 4\ntext: Save, Cancel, Help, Close" }]
212
+ },
213
+ "maxAttempts": 4,
214
+ "limits": { "tokens": 300000, "timeMs": 900000 }
215
+ }
216
+ ```
217
+
218
+ - `{{output}}`, `{{input}}`, `{{id}}` in `command` become one single-quoted word each. Quotes or `;` in an output cannot break the command or run another one. So write them as separate words, never inside quotes or glued to other text (`"{{output}}"` and `x{{output}}` are refused).
219
+ - A command that runs past its time limit is stopped and judged as a `[timed out …]` observation; it never overlaps the next act.
220
+ - `safeToRepeat: true` is required: retries and episodes run the command again. Never use it for anything that must not happen twice (deleting, sending, paying).
221
+ - `isolated` (default `false`): set `true` when each item uses its own state (a new page, a temp folder) so acts may run at the same time. With `false` acts run one at a time.
222
+ - The command follows the session's file policy and sandbox. Commands that need approval are refused. A non-zero exit does not stop the loop; the observation is judged with `[command failed]` in front.
223
+ - Calibration examples give their `observation` directly; the start check never runs the command.
224
+ - Observations are cut at `maxObservationChars` (default 8,000). With `extract`, Pixie shortens them as instructed before judging.
225
+ - On a retry the worker sees the previous observation and the feedback.
226
+
227
+ `probe.mjs` in the example is a small script you write. For instance, with Playwright it opens the file and prints how many elements match and their text:
228
+
229
+ ```js
230
+ // probe.mjs — usage: node probe.mjs <page.html> <selector>
231
+ import { chromium } from 'playwright';
232
+ import { pathToFileURL } from 'node:url';
233
+ const [file, selector] = process.argv.slice(2);
234
+ const browser = await chromium.launch();
235
+ const page = await browser.newPage();
236
+ await page.goto(pathToFileURL(file).href);
237
+ try {
238
+ const found = page.locator(selector);
239
+ console.log(`matches: ${await found.count()}`);
240
+ console.log(`text: ${(await found.allInnerTexts()).join(', ')}`);
241
+ } catch (error) { console.log(`invalid selector: ${error.message.split('\n')[0]}`); process.exitCode = 1; }
242
+ await browser.close();
243
+ ```
244
+
245
+ When writing such a script:
246
+
247
+ - Print only what the judgment needs, as short text. Screenshots cannot be judged.
248
+ - Start each item from a new page or temporary state, so `isolated: true` is safe.
249
+ - Report failures as output (`invalid selector: …`); the engine sees the cause and Pixie writes feedback.
250
+
251
+ ## Episodes and selection
252
+
253
+ `mode: "episode"` treats each output as **one step**. `act` runs every step, and the next step sees the new observation. The episode ends when the checks pass (goal reached), or is escalated after `maxAttempts` steps.
254
+
255
+ With `select`, the worker writes nothing: **the decision engine picks one listed option.** Options come only from JSON `{"options": [name | {"name", "description"}]}` in the item input (`optionsFrom: "input"`) or, in episodes, the latest observation (`optionsFrom: "observation"`). Pixie never writes the options, so an action that is not allowed cannot come out.
256
+
257
+ - A `select` without criteria (pick once) returns the pick as the result and needs no calibration.
258
+ - Below `minConfidence` the item is escalated instead of acting on a guess.
259
+ - At most 255 options; group or filter them in the command first.
260
+
261
+ ### Example: choose actions in a running game
262
+
263
+ Say a command `game-cli` prints the game state and allowed actions as JSON.
264
+
265
+ ```json
266
+ {
267
+ "items": { "inline": [{ "id": "run-1", "input": "Reach the exit without losing all hp." }] },
268
+ "work": { "instruction": "Play one turn per step." },
269
+ "mode": "episode",
270
+ "act": { "tool": "bash", "command": "game-cli step {{output}}", "safeToRepeat": true },
271
+ "select": { "instructions": "Given `state`, which action best moves toward the exit while keeping hp above 0?", "optionsFrom": "observation", "minConfidence": 0.4 },
272
+ "criteria": "The observation shows the player reached the exit.",
273
+ "calibration": {
274
+ "pass": [{ "input": "Reach the exit", "output": "move-east", "observation": "{\"state\":\"at exit\",\"hp\":3,\"options\":[]}" }],
275
+ "fail": [{ "input": "Reach the exit", "output": "move-east", "observation": "{\"state\":\"corridor\",\"hp\":3,\"options\":[\"move-east\",\"wait\"]}" }]
276
+ },
277
+ "maxAttempts": 30,
278
+ "limits": { "tokens": 500000, "timeMs": 1800000 }
279
+ }
280
+ ```
281
+
282
+ - The first step runs the command once with an empty output to get the starting state and options; make `game-cli step ''` print the current state only.
283
+ - The engine sees only the current state. When lookahead matters, put predicted outcomes in option descriptions (`{"name":"attack","description":"enemy hp -3, counterattack likely"}`).
284
+ - Suited to turn-based work with decisions seconds apart, not real-time control.
285
+
286
+ ## Grouping results (route)
287
+
288
+ Give `route.question` the id of a choice question; each item's final answer to it becomes its group. The summary counts each group, and each results line gets `bucket`. With `criteria`, include a sentence for that question; Pixie creates it under the same id.
289
+
290
+ ```json
291
+ {
292
+ "items": { "path": "work/reports.jsonl" },
293
+ "work": { "instruction": "Restate the report as one clear sentence." },
294
+ "checks": {
295
+ "questions": { "kind": { "type": "choice", "instructions": "What kind of report is `input`?", "criteria": { "bug": "a defect", "feature": "a request for new behavior", "unclear": "cannot tell" } } },
296
+ "pass": { "kind": { "accept": ["bug", "feature"] } }
297
+ },
298
+ "route": { "question": "kind" },
299
+ "calibration": {
300
+ "pass": [{ "input": "Saving crashes the app", "output": "Saving crashes the app." }],
301
+ "fail": [{ "input": "hmm", "output": "hmm" }]
302
+ },
303
+ "maxAttempts": 1,
304
+ "limits": { "tokens": 200000, "timeMs": 600000 }
305
+ }
306
+ ```
307
+
308
+ ## Recipes
309
+
310
+ ![Loop recipes page with a saved recipe and the editor](../images/en/pixie-loop-recipes.png)
311
+
312
+ Save specs you reuse under a name in **Studio → Pixie → Gates → Loop recipes**. A recipe is a spec without `items` and `output`. Saving compiles the criteria and checks them against the calibration examples once; a failed check saves nothing. Runs reuse the saved questions without compiling or calibrating again.
313
+
314
+ | Where | How |
315
+ | --- | --- |
316
+ | `pixie_loop` tool | `{ "recipe": "ko-review", "items": {...}, "output"?: "...", "run_in_background"?: true }`; no other fields with it |
317
+ | Pixie Gate | In the gate's advanced options pick the recipe under **Pixie Loop recipe** and **Check** or **Repair**. See the [Pixie Gate guide](pixie-gate.md#advanced-options) |
318
+ | Hook scripts | `pixie loop --recipe ko-review [--mode check|repair] [--input]`. See [the pixie helper](pixie-gate.md#the-pixie-helper) |
319
+
320
+ - Gates and hook scripts run one item (the gate target or `--input`). **Check** judges the target as the output without changing it; **repair** lets the worker rework the target until it passes.
321
+ - Gates and hook scripts cannot run recipes that run commands (`act`), episodes, or selections; use those only with the `pixie_loop` tool.
322
+ - Gate and hook runs are also recorded in the session's **Pixie tab → Loops** with the recipe name.
323
+
324
+ ### Example: check translations whenever a file is written
325
+
326
+ Save a `ko-review` recipe and attach this hook script to a `Tool after` gate.
327
+
328
+ ```sh
329
+ #!/bin/bash
330
+ # Check what the write tool wrote with the recipe.
331
+ [ "$PIXIE_TOOL_NAME" = write ] || exit 10
332
+ content=$(printf '%s' "$PIXIE_TOOL_ARGS" | jq -r .content)
333
+ if result=$(printf '%s' "$content" | pixie loop --recipe ko-review --input --json); then exit 10; fi
334
+ jq -n --arg reason "$(printf '%s' "$result" | jq -r .reason)" '{verdict:"stop", reason:$reason}'
335
+ exit 11
336
+ ```
337
+
338
+ ## Viewing runs
339
+
340
+ ![A run in Pixie tab › Loops: the checks, and an item that passed on the second attempt after Pixie feedback](../images/en/pixie-loop-run.png)
341
+
342
+ Open **Pixie tab → Loops** in the session's right sidebar. Selecting a run shows:
343
+
344
+ - status and progress (`3/10 items`), passed, escalated, and error counts, tokens used, the results file
345
+ - checks: questions, pass conditions, and whether Pixie compiled them from criteria or the spec gave them
346
+ - per item, each attempt's output, failed code checks, decision-engine values, and Pixie's feedback
347
+
348
+ Active runs refresh every 2 seconds. A run in progress when DSH restarts does not resume; it shows as `Cancelled` shortly after.
349
+
350
+ ## What it cannot do
351
+
352
+ - Let the worker use tools or read files. Put the material in `items` or `shared`; when something must run, `act` runs one fixed command for it.
353
+ - Let items reference each other or grow the glossary during a run. Run one more consistency check over the results afterwards.
354
+ - Change criteria or items during a run. Stop and start again.
355
+ - Use different models per stage. Every stage uses the default Pixie model.
356
+ - Judge images or react in real time.
357
+ - Exact checks beyond the three code checks, unless an `act` command prints them for judging.
358
+ - Use `act` for a command that must not run twice.
359
+ - Resume after DSH restarts.
360
+ - With the LLM engine on the same model as Pixie, the producer grades itself. Prefer Jev or a different model.
361
+
362
+ ## Agent skill
363
+
364
+ On start Studio installs a skill with this guide into `<DSH home>/skills/studio-pixie-loop-guide/`. Ask an agent something like "review these strings' translations with pixie_loop" and it reads the skill and writes the spec. Files you edit are not overwritten by updates; delete one and restart DSH to get Studio's version back.