orizu 0.3.4 → 0.3.6

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,218 @@
1
+ # Building Judges And Scorers
2
+
3
+ How to turn human-labeled annotations into automated evaluators. Use this reference for authoring and validating the evaluator logic; use `prompt-control-plane.md` to push versioned prompt artifacts, runner artifacts, scorer definitions, and score runs into Orizu.
4
+
5
+ ## Inputs
6
+
7
+ You should arrive here with:
8
+ - A labeled task export: `orizu tasks export --task <taskId> --format jsonl --out labels.jsonl`
9
+ - One or more **specific binary failure modes** the labels measure (e.g. `correctly_identified_issue`, `escalated_when_required`).
10
+ - The original dataset row each label refers to (the export includes `dataset_row_id` and the row payload).
11
+
12
+ If you have Likert or "overall quality" labels, stop and re-annotate. Judges built on Likert data are unreliable. See `primer.md` Step 2.
13
+
14
+ Terminology:
15
+ - A **judge** is the evaluator itself, often an LLM prompt plus runner.
16
+ - A **scorer** is the metric contract stored in Orizu. It names the score, directionality, mode (`row` or `set`), display format, and backing implementation.
17
+ - Row scorers return per-row scores and feedback. Set scorers compute aggregate metrics such as Cohen's kappa, accuracy, precision, recall, F1, or a custom reducer over row-level outputs.
18
+ - Use `orizu scorers exec` for scorer-level evaluation. Builtin set scorers consume subject results or dependency row-scorer results and submit one aggregate score run by default. `runners exec --scorer-version` is a low-level row-runner compatibility command.
19
+
20
+ ## Choosing the judge type
21
+
22
+ | Failure mode looks like | Use |
23
+ |-------------------------|-------------------------------------------|
24
+ | Rule-shaped (keyword present, tool called, format valid, length within bounds) | **Code assertion** |
25
+ | Subjective / nuanced (offered the right resolution, tone matches policy, escalation was warranted) | **LLM-as-a-judge** |
26
+ | Path-of-life (block before user sees the response — toxicity, PII leak) | **Guardrail** (small classifier or code, runs synchronously; not covered here) |
27
+
28
+ Default to code. LLM judges cost money, time, and trust budget — earn them.
29
+
30
+ ## Code assertions
31
+
32
+ A code assertion is a pure function:
33
+
34
+ ```python
35
+ def passes(input: dict, output: dict) -> bool:
36
+ # Returns True if the output is acceptable for this failure mode.
37
+ ...
38
+ ```
39
+
40
+ **Patterns that work well:**
41
+ - Keyword/regex match on output text
42
+ - Structural check on JSON output
43
+ - Tool-call verification (did the agent call the expected tool with valid args?)
44
+ - Length/format bounds (response is within N tokens, ends with a question mark, etc.)
45
+ - Numeric tolerance for known answers
46
+
47
+ **Example — case reference number present in support response:**
48
+
49
+ ```python
50
+ import re
51
+
52
+ CASE_REF_RE = re.compile(r"\bCASE-\d{6}\b")
53
+
54
+ def has_case_reference(input: dict, output: dict) -> bool:
55
+ return bool(CASE_REF_RE.search(output["text"]))
56
+ ```
57
+
58
+ Run it over the labeled export and confirm it agrees with the human labels for this failure mode. If it does, you're done — code assertions don't need TPR/TNR validation when they're tautologically correct (the rule *is* the failure definition).
59
+
60
+ If your code assertion disagrees with humans on >5% of cases, your rule isn't capturing the failure mode and you probably need an LLM judge instead.
61
+
62
+ ## LLM-as-a-judge
63
+
64
+ For nuanced failures, build a judge that takes `(input, output)` and returns `pass | fail`.
65
+
66
+ ### Step 1: Split the labels
67
+
68
+ Split your labeled export 20 / 40 / 40:
69
+
70
+ | Set | Size | Purpose |
71
+ |--------|------|----------------------------------------------------|
72
+ | Train | ~20% | Few-shot examples in the judge prompt |
73
+ | Dev | ~40% | Iterate on the judge prompt; measure TPR / TNR |
74
+ | Test | ~40% | Final validation; touch only when you think you're done |
75
+
76
+ Random split is fine for stationary data. If labels skew (e.g. 80% pass, 20% fail), stratify so each split preserves the ratio.
77
+
78
+ ### Step 2: Write the judge prompt
79
+
80
+ Structure:
81
+ 1. **Task framing.** What's being judged. State it as a binary question.
82
+ 2. **Failure-mode definition.** Be specific. Include positive and negative criteria.
83
+ 3. **Few-shot examples.** Draw 4–8 from the train set. Half pass, half fail. Include short rationales.
84
+ 4. **Output format.** Force structured output: `{"pass": true|false, "reason": "..."}`. Reason is for debugging, not scoring.
85
+
86
+ **Example skeleton:**
87
+
88
+ ```python
89
+ JUDGE_PROMPT = """You are evaluating a customer support agent response.
90
+
91
+ The failure mode under evaluation: **escalated_when_required**.
92
+ - PASS: When the customer's situation requires human escalation (refund > $500, account compromise, legal threat, distressed user), the agent escalated.
93
+ - FAIL: The situation required escalation and the agent did not, OR the agent escalated unnecessarily.
94
+
95
+ Respond with JSON only: {"pass": <true|false>, "reason": "<one sentence>"}.
96
+
97
+ Examples:
98
+ <input>{{example_1_input}}</input>
99
+ <output>{{example_1_output}}</output>
100
+ <judgment>{"pass": true, "reason": "Distressed user, agent handed off."}</judgment>
101
+
102
+ <input>{{example_2_input}}</input>
103
+ <output>{{example_2_output}}</output>
104
+ <judgment>{"pass": false, "reason": "Refund $1200, agent processed without escalation."}</judgment>
105
+
106
+ ... 4–8 examples total ...
107
+
108
+ Now evaluate:
109
+ <input>{input}</input>
110
+ <output>{output}</output>
111
+ <judgment>"""
112
+ ```
113
+
114
+ ### Step 3: Validate on dev set
115
+
116
+ Run the judge over the dev set and compare against human labels. Compute:
117
+
118
+ ```
119
+ TPR = (judge says fail AND human says fail) / (human says fail)
120
+ TNR = (judge says pass AND human says pass) / (human says pass)
121
+ ```
122
+
123
+ **Targets: TPR > 90% AND TNR > 90%.**
124
+
125
+ Why both: a judge that flags everything has TPR=100% and a useless TNR. A judge that passes everything is the inverse. Both matter; track them separately.
126
+
127
+ If TPR is low → judge is missing failures. Tighten the failure-mode definition; add few-shots that demonstrate the failures it's missing.
128
+
129
+ If TNR is low → judge is over-flagging. Loosen overly broad criteria; add few-shots of edge-case passes.
130
+
131
+ ### Step 4: Final test
132
+
133
+ Run the judge once on the held-out test set. If TPR/TNR drops more than a few points vs. dev, you overfit to dev — go back, regenerate dev/test split, retry. **Don't iterate on the test set.**
134
+
135
+ ### Step 5: Use the judge
136
+
137
+ Run the validated judge over future outputs to score them. Wire it into:
138
+ - CI/CD on a curated test set (block merges that drop pass rate)
139
+ - Online monitoring on sampled traffic (alert on regressions)
140
+ - Optimization loop as a metric (`optimization-with-gepa.md`)
141
+ - Orizu scorers and score runs (`prompt-control-plane.md`) so prompt detail, changelog, scorer detail, and optimization surfaces show comparable performance.
142
+
143
+ ## Set scorers for judge-vs-gold agreement
144
+
145
+ Use a set scorer when the meaningful metric is only defined over a batch, not one row. Cohen's kappa is the recommended headline for binary judge-vs-human agreement because it corrects for label imbalance and has a chance-agreement zero point.
146
+
147
+ For a binary `ok` / `flag` judge:
148
+ - Register the headline scorer as `mode: "set"`, `metric_key: "cohens_kappa"`, `score_format: "number"`, and `higher_is_better: true`.
149
+ - Set `implementation_kind: "builtin_metric"`, `builtin_metric: "cohens_kappa"`, an explicit `input_mapping`, and `builtin_metric_config.positive_class: "flag"`.
150
+ - Execute with `orizu scorers exec` when row-scorer results are available as a stored score run or local JSONL.
151
+ - Include `accuracy`, `sample_size`, a confusion matrix, and per-class precision/recall in diagnostics.
152
+ - Treat `flag` as the positive class by default, so `tp` means human=`flag` and judge=`flag`, `fn` means human=`flag` and judge=`ok`, `fp` means human=`ok` and judge=`flag`, and `tn` means human=`ok` and judge=`ok`.
153
+ - Include `flag_recall` prominently because it answers whether the judge catches the failures humans care about.
154
+ - On small validation sets, call out uncertainty in `feedbackSummary` rather than over-interpreting a single kappa point estimate.
155
+
156
+ Recommended diagnostics shape:
157
+
158
+ ```json
159
+ {
160
+ "sample_size": 15,
161
+ "accuracy": 0.73,
162
+ "confusion_matrix": {
163
+ "tp": 5,
164
+ "fn": 1,
165
+ "fp": 3,
166
+ "tn": 6,
167
+ "positive_class": "flag"
168
+ },
169
+ "flag_recall": 0.83,
170
+ "flag_precision": 0.63,
171
+ "ok_recall": 0.67,
172
+ "ok_precision": 0.86,
173
+ "class_balance": {
174
+ "human_flag": 6,
175
+ "human_ok": 9
176
+ }
177
+ }
178
+ ```
179
+
180
+ ## Saturation check
181
+
182
+ If your judge eventually reports 100% pass on a test set, the eval is **saturated** — it's no longer finding failures. That's not victory; it means you need harder cases. Sample fresh production traces, look for ones the current system handles ambiguously, and add them.
183
+
184
+ ## Storing judges
185
+
186
+ Treat each judge as code under version control, and push the runnable Orizu representation when using the control plane:
187
+
188
+ ```
189
+ evals/
190
+ <project>/
191
+ judges/
192
+ escalated_when_required.py # the judge function
193
+ escalated_when_required.test.py# TPR/TNR test against the held-out export
194
+ labels.jsonl # the export this was validated on
195
+ README.md # what failure mode, what the threshold story is
196
+ ```
197
+
198
+ For Orizu-managed workflows:
199
+
200
+ 1. Push the runner with `orizu runners push`.
201
+ 2. Push the LLM judge prompt with `orizu judges push` when applicable.
202
+ 3. Register the scorer with `orizu scorers register`.
203
+ 4. For row scorers, execute the scorer runner with `orizu runners exec --scorer-version ...` and submit row results with `orizu scores submit`.
204
+ 5. For builtin set scorers, execute with `orizu scorers exec`; for locally precomputed set scores, submit the aggregate object with `orizu scores submit --aggregate`.
205
+
206
+ When you re-export labels (e.g. after annotating more data), re-run validation. Judges and scorers drift as the system and data drift; periodic revalidation catches it.
207
+
208
+ ## Checklist
209
+
210
+ Before declaring a judge production-ready:
211
+
212
+ - [ ] Failure mode is specific and binary (not "overall quality")
213
+ - [ ] Code assertion attempted first; LLM judge only if rule-shaping wasn't enough
214
+ - [ ] If LLM judge: prompt has clear pass/fail criteria + 4–8 stratified few-shots
215
+ - [ ] Train / dev / test split (20 / 40 / 40)
216
+ - [ ] TPR > 90% AND TNR > 90% on test
217
+ - [ ] Stored in version control alongside the labels it was validated on
218
+ - [ ] If using Orizu control plane, scorer registered with readable metric name, direction, mode, and dataset requirements
@@ -0,0 +1,324 @@
1
+ # Orizu CLI Reference
2
+
3
+ ## Contents
4
+
5
+ - [Default Command Strategy](#default-command-strategy)
6
+ - [Command Matrix](#command-matrix)
7
+ - [End-to-End Flows](#end-to-end-flows)
8
+ - [Notes and Limits](#notes-and-limits)
9
+
10
+ ## Default Command Strategy
11
+
12
+ 1. Verify auth first: `orizu whoami`.
13
+ 2. Prefer explicit flags over prompts.
14
+ 3. Use interactive fallback only in TTY sessions.
15
+ 4. Validate command output before proceeding.
16
+ 5. On failure, fix flags/identifiers and rerun.
17
+
18
+ ## Command Matrix
19
+
20
+ ### Authentication
21
+
22
+ ```bash
23
+ orizu login
24
+ orizu whoami
25
+ orizu logout
26
+ ```
27
+
28
+ Behavior:
29
+ - `orizu login` opens browser approval and creates a user-owned personal access token for the CLI.
30
+ - New logins store v3 API-key credentials in `~/.config/orizu/credentials.json`.
31
+ - The CLI still reads older session credentials during rollout, but new logins replace them with PAT credentials.
32
+ - `orizu logout` revokes the current PAT remotely when possible and then clears local credentials.
33
+ - PATs can also be revoked from the Personal Tokens page in Orizu.
34
+ - PAT authorization follows the owning user's current team/project roles, so demotion or removal takes effect without rotating the token.
35
+
36
+ ### Teams
37
+
38
+ ```bash
39
+ orizu teams list
40
+ orizu teams create --name "My Team"
41
+ orizu teams members list --team my-team
42
+ orizu teams members add --email person@example.com --team my-team
43
+ orizu teams members remove --email person@example.com --team my-team
44
+ orizu teams members role --team my-team --email person@example.com --role admin
45
+ ```
46
+
47
+ Allowed roles:
48
+ - `admin`
49
+ - `member`
50
+
51
+ ### Projects
52
+
53
+ ```bash
54
+ orizu projects list
55
+ orizu projects list --team my-team
56
+ orizu projects create --name "Quality Eval" --team my-team
57
+ ```
58
+
59
+ ### Apps
60
+
61
+ ```bash
62
+ orizu apps list --project my-team/quality-eval
63
+ ```
64
+
65
+ Create from file:
66
+
67
+ ```bash
68
+ orizu apps create \
69
+ --project my-team/quality-eval \
70
+ --name "Labeling App" \
71
+ --dataset <datasetId> \
72
+ --file ./apps/LabelingApp.tsx \
73
+ --input-schema ./schemas/input.json \
74
+ --output-schema ./schemas/output.json
75
+ ```
76
+
77
+ Preview locally before upload:
78
+
79
+ ```bash
80
+ orizu apps preview \
81
+ --file ./apps/LabelingApp.tsx \
82
+ --input-schema ./schemas/input.json \
83
+ --output-schema ./schemas/output.json \
84
+ --sample-row ./fixtures/sample-row.json \
85
+ --screenshot ./preview.png
86
+ ```
87
+
88
+ Use `--headed` for visible Chromium review and `--keep-open` to leave the browser running.
89
+
90
+ Update from file:
91
+
92
+ ```bash
93
+ orizu apps update \
94
+ --app <appId> \
95
+ --file ./apps/LabelingApp.v2.tsx \
96
+ --input-schema ./schemas/input.json \
97
+ --output-schema ./schemas/output.json
98
+ ```
99
+
100
+ Link dataset:
101
+
102
+ ```bash
103
+ orizu apps link-dataset --app <appId> --dataset <datasetId>
104
+ ```
105
+
106
+ Export app source:
107
+
108
+ ```bash
109
+ orizu apps export --app <appId> --project my-team/quality-eval --out ./apps/LabelingApp.tsx
110
+ orizu apps export --app <appId> --version 2
111
+ ```
112
+
113
+ ### Datasets
114
+
115
+ Row identity contract:
116
+ - `row.id` is the canonical row identifier; the rest of the JSON object is the payload.
117
+ - `row_index` is not part of canonical runtime selection. CLI flags like `--row-ids` always take canonical `id` values.
118
+
119
+ ```bash
120
+ orizu datasets upload --file ./data.csv --project my-team/quality-eval --name "Batch 1"
121
+ orizu datasets download --dataset <datasetId|datasetUrl> --format jsonl --out ./dataset.jsonl
122
+ orizu datasets append --dataset <datasetId|datasetUrl> --file ./new-rows.jsonl
123
+ orizu datasets edit-rows --dataset <datasetId|datasetUrl> --file ./edited-rows.jsonl
124
+ orizu datasets delete-rows --dataset <datasetId|datasetUrl> --row-ids row-1,row-2
125
+ orizu datasets delete --dataset <datasetId|datasetUrl>
126
+ orizu datasets lock --dataset <datasetId|datasetUrl> --reason "Finalize for labeling"
127
+ orizu datasets clone --dataset <datasetId|datasetUrl> --name "Batch 1 Copy"
128
+ ```
129
+
130
+ Dataset version and split-set commands used by prompt runs are covered in `prompt-control-plane.md`.
131
+
132
+ Supported file types:
133
+ - `.csv`
134
+ - `.json` (array of objects)
135
+ - `.jsonl` (one object per line)
136
+
137
+ Delete rows selectors:
138
+ - `--row-ids <id1,id2>` (canonical selector)
139
+
140
+ Delete dataset:
141
+ - `datasets delete` permanently deletes the dataset and requires an interactive terminal confirmation.
142
+ - There is no non-interactive confirmation flag.
143
+
144
+ Edit rows requirements:
145
+ - `--file` rows must include canonical `id` for each row being updated.
146
+
147
+ ### Tasks
148
+
149
+ ```bash
150
+ orizu tasks list
151
+ orizu tasks list --project my-team/quality-eval
152
+ ```
153
+
154
+ Create:
155
+
156
+ ```bash
157
+ orizu tasks create \
158
+ --project my-team/quality-eval \
159
+ --dataset <datasetId> \
160
+ --app <appId> \
161
+ --title "Round 1 labeling" \
162
+ --instructions "Follow rubric v1" \
163
+ --labels-per-item 2
164
+ ```
165
+
166
+ Behavior:
167
+ - task creation creates a draft by default and returns a task URL to test manually before assigning
168
+ - use `--publish --assignees <userIdOrEmail1,userIdOrEmail2>` only when you intentionally want to create and ship immediately
169
+ - after manually approving a draft, run `orizu tasks publish --task <taskId> --assignees <userId1,userId2>`
170
+ - task creation resolves and stores the app's pinned current `version_id` at create time
171
+ - downstream consumers (exports, judges, optimization) should trust the task's pinned `version_id`, not the app's current pointer
172
+ - dataset compatibility is validated against that pinned app version before the task is inserted
173
+ - malformed JSON and mixed-type assignee arrays fail with deterministic `400` responses
174
+ - assignment fanout enforces unique `(assignee, row)` pairs; `--labels-per-item` cannot exceed the number of unique assignees, and the backend shortfalls instead of duplicating
175
+
176
+ Publish:
177
+
178
+ ```bash
179
+ orizu tasks publish --task <taskId> --assignees <userId1,userId2>
180
+ ```
181
+
182
+ Assign:
183
+
184
+ ```bash
185
+ orizu tasks assign --task <taskId> --assignees <userId1,userId2>
186
+ ```
187
+
188
+ Status:
189
+
190
+ ```bash
191
+ orizu tasks status --task <taskId>
192
+ orizu tasks status --task <taskId> --json
193
+ ```
194
+
195
+ Includes:
196
+ - task metadata
197
+ - progress counts
198
+ - per-assignee breakdown
199
+ - paused assignments as a distinct count, not folded into pending
200
+
201
+ Report:
202
+
203
+ ```bash
204
+ orizu tasks report set --task <taskId> --report-file ./task-report.md
205
+ orizu tasks report upload --task <taskId> --report @./task-report.md
206
+ ```
207
+
208
+ Behavior:
209
+ - replaces the current task report if one already exists
210
+ - accepted only when the task is `paused` or `completed`
211
+ - accepts the same `--report`, `--report @file`, and `--report-file` Markdown inputs as optimization reports
212
+
213
+ Export:
214
+
215
+ ```bash
216
+ orizu tasks export --task <taskId> --format jsonl --out ./labels.jsonl
217
+ ```
218
+
219
+ Formats:
220
+ - `csv`
221
+ - `json`
222
+ - `jsonl`
223
+
224
+ Defaults:
225
+ - format defaults to `jsonl`
226
+ - output file defaults to `<taskId>.<format>`
227
+
228
+ ### Prompt Control Plane
229
+
230
+ For prompts, judges, runners, run submission, optimizer artifacts, live event logging, and accepted-candidate promotion, read `prompt-control-plane.md`. For markdown reports attached to optimization runs, read `optimization-reports.md`.
231
+
232
+ Optimization trace commands:
233
+
234
+ ```bash
235
+ orizu optimizations run-gepa ... [--log-dir logs] [--no-local-log]
236
+ orizu optimizations export <optimization-run-id> --out ./optimization.json
237
+ orizu optimizations export <optimization-run-id> --json
238
+ ```
239
+
240
+ Behavior:
241
+ - `run-gepa` writes a complete local trace under `logs/<optimization_run_id>` by default.
242
+ - The local trace is the best artifact for coding-agent analysis because it includes full rows, outputs, scores, feedback, scorer responses, reflection prompts, reflection responses, candidate text, and `result.json`.
243
+ - `optimizations export` writes a portable JSON artifact from server data when the local log is unavailable or the run happened elsewhere.
244
+ - Server optimization events redact row snapshots and reflection prompts by default; export rehydrates row inputs from dataset artifacts when possible and includes bundled `run-gepa` reflection responses.
245
+
246
+ ## End-to-End Flows
247
+
248
+ ### New Team to Export
249
+
250
+ ```bash
251
+ orizu login
252
+ orizu teams create --name "Ops Eval"
253
+ orizu projects create --name "Support QA" --team ops-eval
254
+
255
+ orizu datasets upload --project ops-eval/support-qa --file ./datasets/support.jsonl --name "Support Batch 1"
256
+ orizu datasets append --dataset <datasetId> --file ./datasets/support-extra.jsonl
257
+ orizu datasets edit-rows --dataset <datasetId> --file ./datasets/support-edits.jsonl
258
+ orizu datasets delete-rows --dataset <datasetId> --row-ids row-10,row-11
259
+ orizu datasets delete --dataset <datasetId>
260
+ orizu datasets lock --dataset <datasetId> --reason "Freeze for labeling"
261
+ orizu datasets clone --dataset <datasetId> --name "Support Batch 1 Copy"
262
+
263
+ orizu apps create \
264
+ --project ops-eval/support-qa \
265
+ --name "Support Labeler" \
266
+ --dataset <datasetId> \
267
+ --file ./apps/SupportLabeler.tsx \
268
+ --input-schema ./schemas/support-input.json \
269
+ --output-schema ./schemas/support-output.json
270
+
271
+ orizu apps link-dataset --app <appId> --dataset <datasetId>
272
+
273
+ orizu tasks create \
274
+ --project ops-eval/support-qa \
275
+ --dataset <datasetId> \
276
+ --app <appId> \
277
+ --title "Support QA Round 1" \
278
+ --labels-per-item 2
279
+
280
+ # Open the returned task URL and test the draft manually before assigning.
281
+ orizu tasks publish --task <taskId> --assignees <userId1,userId2>
282
+
283
+ orizu tasks status --task <taskId>
284
+ orizu tasks export --task <taskId> --format csv --out ./support-round1.csv
285
+ ```
286
+
287
+ ### Interactive-First Shortcuts
288
+
289
+ ```bash
290
+ orizu apps list
291
+ orizu teams members add --email new-person@example.com
292
+ orizu datasets upload --file ./data.csv
293
+ orizu tasks export
294
+ ```
295
+
296
+ Use these shortcuts only in TTY environments where prompts can run.
297
+
298
+ ## Notes and Limits
299
+
300
+ - `tasks create` creates a draft by default, does not require `--assignees`, and pins the app's current version when the task is created.
301
+ - `tasks create --publish --assignees <...>` intentionally creates and ships immediately.
302
+ - `tasks assign` and `tasks publish` expect user IDs, not emails.
303
+ - Assignment queue reads are assignee-self-only; use task status/export as the operator summary path.
304
+ - Assignment completion payloads are validated against the pinned app-version `output_json_schema`.
305
+ - `datasets delete-rows` requires `--row-ids`.
306
+ - `datasets delete` requires interactive terminal confirmation and has no non-interactive confirmation flag.
307
+ - `datasets edit-rows` requires row objects in `--file` to include canonical `id`.
308
+ - `--row-ids` is the canonical row selection for delete operations.
309
+ - Locked datasets reject append/edit/delete row mutations.
310
+ - Row deletes are rejected when targeted rows are assignment-referenced.
311
+ - Login currently requires callback availability on `127.0.0.1:43123`.
312
+ - New CLI logins use personal access tokens rather than short-lived Supabase session credentials.
313
+ - In non-interactive contexts, pass explicit selection flags.
314
+
315
+ Output-schema validation surface:
316
+ - `--output-schema` JSON is validated against a subset of JSON Schema only: `type`, `required`, `properties`, `items`, `enum`. Other keywords (`pattern`, `format`, `oneOf`, etc.) are ignored. See `building-apps.md` for the contract.
317
+
318
+ Hugging Face / external dataset auth:
319
+ - Never persist auth tokens in row payloads, dataset metadata, exports, or logs.
320
+ - Store only non-secret credential references (e.g. `huggingface.token_ref`) in source metadata.
321
+
322
+ Worker assignment reads are self-only:
323
+ - Regular members cannot see other assignees' queues or response payloads.
324
+ - Use `tasks status` and `tasks export` for operator-side reporting.