orizu 0.3.7 → 0.4.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.
@@ -2,14 +2,31 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ import random
6
+ import socket
5
7
  import ssl
6
8
  import subprocess
7
9
  import tempfile
10
+ import time
8
11
  import urllib.error
9
12
  import urllib.request
13
+ from collections.abc import Callable
10
14
  from typing import Any
11
15
 
12
- from .optimizer import ReflectionResult, RowEvaluation, TextGepaConfig, build_reflection_prompt, extract_candidate_text
16
+ from .optimizer import (
17
+ ReflectionResult,
18
+ RetryableReflectionError,
19
+ RowEvaluation,
20
+ TextGepaConfig,
21
+ build_reflection_prompt,
22
+ extract_candidate_text,
23
+ )
24
+
25
+
26
+ RETRYABLE_HTTP_STATUS_CODES = {429, 500, 502, 503, 504, 529}
27
+ RETRY_BACKOFF_BASE_SECONDS = 5.0
28
+ RETRY_BACKOFF_JITTER_SECONDS = 5.0
29
+ RETRYABLE_DIRECT_ERRORS = (TimeoutError, socket.timeout, ConnectionResetError, ConnectionAbortedError)
13
30
 
14
31
 
15
32
  def _read_anthropic_response_with_curl(api_key: str, payload: dict[str, Any]) -> dict[str, Any]:
@@ -40,6 +57,73 @@ def _read_anthropic_response_with_curl(api_key: str, payload: dict[str, Any]) ->
40
57
  return json.loads(result.stdout)
41
58
 
42
59
 
60
+ def _validate_positive_http_config(value: int, name: str) -> int:
61
+ if value <= 0:
62
+ raise RuntimeError(f"{name} must be positive")
63
+ return value
64
+
65
+
66
+ def _retry_backoff_seconds(attempt: int) -> float:
67
+ return (RETRY_BACKOFF_BASE_SECONDS * (2 ** attempt)) + random.uniform(0, RETRY_BACKOFF_JITTER_SECONDS)
68
+
69
+
70
+ def _is_retryable_http_status(status_code: int) -> bool:
71
+ return status_code in RETRYABLE_HTTP_STATUS_CODES
72
+
73
+
74
+ def _is_retryable_url_error(error: urllib.error.URLError) -> bool:
75
+ return isinstance(error.reason, RETRYABLE_DIRECT_ERRORS)
76
+
77
+
78
+ def _read_json_response_with_retries(
79
+ request: urllib.request.Request,
80
+ *,
81
+ timeout_seconds: int,
82
+ retry_attempts: int,
83
+ failure_prefix: str,
84
+ ssl_fallback: Callable[[], dict[str, Any]] | None = None,
85
+ ) -> dict[str, Any]:
86
+ timeout_seconds = _validate_positive_http_config(timeout_seconds, "reflection_http_timeout_seconds")
87
+ retry_attempts = _validate_positive_http_config(retry_attempts, "reflection_retry_attempts")
88
+
89
+ for attempt in range(retry_attempts):
90
+ try:
91
+ with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
92
+ return json.loads(response.read().decode("utf-8"))
93
+ except urllib.error.HTTPError as error:
94
+ if _is_retryable_http_status(error.code) and attempt < retry_attempts - 1:
95
+ time.sleep(_retry_backoff_seconds(attempt))
96
+ continue
97
+ detail = error.read().decode("utf-8", errors="replace")
98
+ if _is_retryable_http_status(error.code):
99
+ raise RetryableReflectionError(
100
+ f"{failure_prefix} retryable HTTP failure after {retry_attempts} attempts: "
101
+ f"{error.code} {detail}"
102
+ ) from error
103
+ raise RuntimeError(f"{failure_prefix}: {error.code} {detail}") from error
104
+ except urllib.error.URLError as error:
105
+ if isinstance(error.reason, ssl.SSLCertVerificationError) and ssl_fallback is not None:
106
+ return ssl_fallback()
107
+ if _is_retryable_url_error(error):
108
+ if attempt < retry_attempts - 1:
109
+ time.sleep(_retry_backoff_seconds(attempt))
110
+ continue
111
+ raise RetryableReflectionError(
112
+ f"{failure_prefix} retryable connection failure after {retry_attempts} attempts: "
113
+ f"{error.reason}"
114
+ ) from error
115
+ raise
116
+ except RETRYABLE_DIRECT_ERRORS as error:
117
+ if attempt < retry_attempts - 1:
118
+ time.sleep(_retry_backoff_seconds(attempt))
119
+ continue
120
+ raise RetryableReflectionError(
121
+ f"{failure_prefix} timed out after {retry_attempts} attempts: {error}"
122
+ ) from error
123
+
124
+ raise RetryableReflectionError(f"{failure_prefix} failed after {retry_attempts} attempts")
125
+
126
+
43
127
  def _merge_provider_settings(
44
128
  payload: dict[str, Any],
45
129
  settings: dict[str, Any],
@@ -143,16 +227,13 @@ def reflect_with_anthropic(parent_text: str, parent_results: list[RowEvaluation]
143
227
  "content-type": "application/json",
144
228
  },
145
229
  )
146
- try:
147
- with urllib.request.urlopen(request, timeout=180) as response:
148
- data = json.loads(response.read().decode("utf-8"))
149
- except urllib.error.HTTPError as error:
150
- detail = error.read().decode("utf-8", errors="replace")
151
- raise RuntimeError(f"Reflection LM failed: {error.code} {detail}") from error
152
- except urllib.error.URLError as error:
153
- if not isinstance(error.reason, ssl.SSLCertVerificationError):
154
- raise
155
- data = _read_anthropic_response_with_curl(api_key, payload)
230
+ data = _read_json_response_with_retries(
231
+ request,
232
+ timeout_seconds=config.reflection_http_timeout_seconds,
233
+ retry_attempts=config.reflection_retry_attempts,
234
+ failure_prefix="Reflection LM failed",
235
+ ssl_fallback=lambda: _read_anthropic_response_with_curl(api_key, payload),
236
+ )
156
237
  parts = data.get("content") or []
157
238
  text = "".join(part.get("text", "") for part in parts if isinstance(part, dict))
158
239
  return ReflectionResult(
@@ -180,12 +261,12 @@ def reflect_with_openai(parent_text: str, parent_results: list[RowEvaluation], c
180
261
  "Content-Type": "application/json",
181
262
  },
182
263
  )
183
- try:
184
- with urllib.request.urlopen(request, timeout=180) as response:
185
- data = json.loads(response.read().decode("utf-8"))
186
- except urllib.error.HTTPError as error:
187
- detail = error.read().decode("utf-8", errors="replace")
188
- raise RuntimeError(f"OpenAI reflection LM failed: {error.code} {detail}") from error
264
+ data = _read_json_response_with_retries(
265
+ request,
266
+ timeout_seconds=config.reflection_http_timeout_seconds,
267
+ retry_attempts=config.reflection_retry_attempts,
268
+ failure_prefix="OpenAI reflection LM failed",
269
+ )
189
270
  text = _extract_openai_output_text(data)
190
271
  if not text:
191
272
  raise RuntimeError("OpenAI reflection LM returned no output text")
@@ -22,15 +22,18 @@ Skip this skill when:
22
22
 
23
23
  - Node.js 20+ installed.
24
24
  - Install the CLI: `npm i -g orizu`.
25
- - Install this skill from the CLI package: `orizu install-skill --target agent-user --yes`.
25
+ - Install this skill from the CLI package: `orizu install-skill --agent codex --yes` (or `--agent claude`; advanced target IDs remain available via `--target`).
26
+ - Or read the bundled skill in place without installing: `orizu skills path --json` returns `root`, `skillMd`, `source`, `cliVersion`, and `skillHash`.
27
+ - After CLI upgrades, check installed copies with `orizu skills status` and refresh them with `orizu skills update`.
26
28
  - Rediscover the command surface any time with `orizu --help`, `orizu <group> --help`, or `orizu capabilities --json`.
29
+ - Every command supports `--json` (prefix `orizu --json <cmd>` or trailing flag) and emits a single JSON document; prefer it over parsing human-formatted text. Long-running commands emit the JSON summary as the final stdout line.
27
30
  - Login callback requires `127.0.0.1:43123`.
28
31
  - Credentials are stored at `~/.config/orizu/credentials.json`.
29
32
  - Current `orizu login` creates a user-owned personal access token for the CLI and stores it as a v3 API-key credential. The raw token is not recoverable after creation.
30
33
 
31
34
  ## Login
32
35
 
33
- - Verify auth: `orizu whoami`.
36
+ - Verify auth: `orizu whoami --json` (returns `{user, server}`; non-zero exit means sign in is needed).
34
37
  - Start the login flow: `orizu login` (opens a browser tab).
35
38
  - Approving login in the browser creates a personal access token for that Orizu user. The token inherits the user's current team/project access and loses access if the user's role or membership changes.
36
39
  - Clear local credentials and revoke the current CLI token: `orizu logout`.
@@ -143,12 +146,14 @@ Local execution workflow:
143
146
  7. Diff before/after on a held-out set; ship if it holds.
144
147
 
145
148
  Bundled `run-gepa` reflection behavior:
149
+ - Budget controls are mutually exclusive. Use at most one of `--budget auto|light|medium|heavy`, `--max-metric-calls <n>`, `--max-full-evals <n>`, or `--max-iterations <n>`. If none is provided, `run-gepa` uses `--budget auto`, the balanced medium preset.
146
150
  - The reflective LM's final text is used verbatim as the next candidate prompt. It should return only the complete updated prompt body, not analysis, labels, XML tags, or markdown fences.
147
151
  - Keep provider-native reasoning controls separate from the prompt text with `--reflection-provider-settings <json|@file>`.
148
152
  - Reflection max-token limits are explicit config, not implicit defaults. Use `--reflection-max-tokens <n>` when a provider requires an output cap; this maps to Anthropic `max_tokens` and OpenAI `max_output_tokens`.
149
153
  - Anthropic native Messages reflection requires `max_tokens`, so `run-gepa` with an `anthropic/...` reflection model must receive `--reflection-max-tokens <n>` and should fail fast if it is omitted. OpenAI reflection may omit `max_output_tokens` unless the user explicitly wants a cap.
154
+ - Reflection HTTP calls retry transient provider failures by default (`--reflection-retry-attempts 3`, `--reflection-http-timeout-seconds 180`). Retryable failures include timeouts, connection reset/abort, HTTP 429, and common server/capacity statuses. If reflection still fails after retries, `run-gepa` logs `reflection_failed`, counts the proposal against budget, and continues to the next iteration.
150
155
  - By default, `run-gepa` skips reflection and child creation when every row in the selected parent mini-batch is already perfect (`1.0` for higher-is-better scorers, `0.0` for lower-is-better scorers). This saves reflection tokens because the child cannot beat a saturated mini-batch. Use `--no-skip-perfect-parent-reflection` to force reflection anyway, or `--skip-perfect-parent-reflection` to make the default explicit in scripted runs.
151
- - By default, `run-gepa` sets `--num-threads auto` for row evaluations. Auto uses mini-batch size, validation-set size, CPU count, memory estimate, file-descriptor limit, and an 8-thread hard cap to avoid launching too many runner subprocesses at once. Use `--num-threads <n>` only when the runner/provider capacity is known.
156
+ - By default, `run-gepa` sets `--num-threads auto` for row evaluations. Auto uses mini-batch size, validation-set size, 2x CPU count, memory estimate, file-descriptor limit, and a 64-thread default ceiling to avoid launching too many runner subprocesses at once while still scaling large runs. Set `ORIZU_GEPA_AUTO_THREADS_MAX` or use `--num-threads <n>` only when the runner/provider capacity is known.
152
157
  - `run-gepa` writes complete local logs by default under `logs/<optimization_run_id>`; override with `--log-dir <dir>` or disable with `--no-local-log`.
153
158
  - Server optimization events redact row snapshots and reflection prompts by default; the local log keeps full row inputs, outputs, scores, feedback, reflection prompts, and reflection responses for later agent analysis.
154
159
  - OpenAI example: `--reflection-model openai/gpt-5 --reflection-provider-settings '{"reasoning":{"effort":"medium","summary":"auto"}}'`.
@@ -12,8 +12,9 @@
12
12
  1. Verify auth first: `orizu whoami`.
13
13
  2. Prefer explicit flags over prompts.
14
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.
15
+ 4. Prefer `--json` for machine-readable output — every command supports it (prefix `orizu --json <cmd>` or trailing flag). Long-running commands emit the JSON summary as the final stdout line.
16
+ 5. Validate command output before proceeding.
17
+ 6. On failure, fix flags/identifiers and rerun.
17
18
 
18
19
  ## Command Matrix
19
20
 
@@ -33,6 +34,25 @@ Behavior:
33
34
  - PATs can also be revoked from the Personal Tokens page in Orizu.
34
35
  - PAT authorization follows the owning user's current team/project roles, so demotion or removal takes effect without rotating the token.
35
36
 
37
+ ### Agent Setup
38
+
39
+ ```bash
40
+ orizu capabilities --json
41
+ orizu skills path [--skill-md] [--json]
42
+ orizu skills status [--json]
43
+ orizu skills update [--dry-run] [--json]
44
+ orizu install-skill [--agent <claude|codex>]... [--scope global|project] [--mode auto|link|copy] [--yes] [--dry-run] [--json]
45
+ orizu setup [--agent <claude|codex>]... [--skip-login] [--no-install] [--workspace|--no-workspace] [--no-handoff] [--yes] [--dry-run] [--no-input] [--json]
46
+ orizu setup prompt [--json]
47
+ ```
48
+
49
+ Behavior:
50
+ - `capabilities --json` returns the full machine-readable command manifest, including `globalOptions`.
51
+ - `skills path --json` returns where this skill's source lives (`root`, `skillMd`, `source`, `cliVersion`, `skillHash`) so you can read it without installing, and verify an installed copy matches the CLI.
52
+ - `skills status` reports each install target as missing/current/stale/broken-link; `skills update` refreshes stale copies and repairs broken symlinks.
53
+ - `install-skill` selects targets by agent name; `--scope global` (default) installs user-level, `--scope project` into the repo. Project-scope installs always copy; global installs symlink when the CLI path is stable. Advanced target IDs remain available via `--target`.
54
+ - `orizu setup` is the guided onboarding flow (login → agent integration → `.orizu/` workspace → handoff prompt). `orizu setup prompt` prints the repo-adoption prompt for handing to a coding agent.
55
+
36
56
  ### Teams
37
57
 
38
58
  ```bash
@@ -232,13 +252,14 @@ For prompts, judges, runners, run submission, optimizer artifacts, live event lo
232
252
  Optimization trace commands:
233
253
 
234
254
  ```bash
235
- orizu optimizations run-gepa ... [--num-threads auto|N] [--log-dir logs] [--no-local-log]
255
+ orizu optimizations run-gepa ... [--num-threads auto|N] [--reflection-retry-attempts 3] [--reflection-http-timeout-seconds 180] [--log-dir logs] [--no-local-log]
236
256
  orizu optimizations export <optimization-run-id> --out ./optimization.json
237
257
  orizu optimizations export <optimization-run-id> --json
238
258
  ```
239
259
 
240
260
  Behavior:
241
- - `run-gepa` defaults `--num-threads` to `auto`, resolving a conservative row-evaluation parallelism cap from mini-batch size, validation-set size, CPU count, memory estimate, file-descriptor limit, and an 8-thread hard cap.
261
+ - `run-gepa` defaults `--num-threads` to `auto`, resolving a row-evaluation parallelism cap from mini-batch size, validation-set size, 2x CPU count, memory estimate, file-descriptor limit, and a 64-thread default ceiling. Set `ORIZU_GEPA_AUTO_THREADS_MAX` or use `--num-threads <n>` only when the runner/provider capacity is known.
262
+ - `run-gepa` retries transient reflection-provider failures by default. If retries are exhausted, it logs `reflection_failed`, counts that reflection proposal against budget, and continues with the next iteration.
242
263
  - `run-gepa` writes a complete local trace under `logs/<optimization_run_id>` by default.
243
264
  - 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`.
244
265
  - `optimizations export` writes a portable JSON artifact from server data when the local log is unavailable or the run happened elsewhere.
@@ -130,9 +130,9 @@ Set an explicit expectation before the run: for example, "if best kappa does not
130
130
 
131
131
  ### Budget Versus Iteration Cap
132
132
 
133
- Default budget presets are DSPy-style metric-call budgets. `light`, `medium`, and `heavy` map to DSPy's `auto="light"`, `auto="medium"`, and `auto="heavy"` scales; `auto` is treated as the balanced `medium` preset. The final metric-call limit scales with validation-set size, rather than using fixed tiny constants. `--max-metric-calls` overrides this directly, while `--max-full-evals` is converted to a metric-call budget over `train + validation` rows.
133
+ Default budget presets are DSPy-style metric-call budgets. `light`, `medium`, and `heavy` map to DSPy's `auto="light"`, `auto="medium"`, and `auto="heavy"` scales; `auto` is treated as the balanced `medium` preset and is the default when no budget control is provided. The final metric-call limit scales with validation-set size, rather than using fixed tiny constants. `--budget`, `--max-metric-calls`, `--max-full-evals`, and `--max-iterations` are mutually exclusive CLI budget controls; choose only one.
134
134
 
135
- Budget is checked only between iterations. Once an iteration starts, `run-gepa` completes the parent minibatch, child minibatch, and full validation eval for an accepted child even if that overshoots the nominal budget. If a run ends as `budget_exhausted`, it pauses and does not auto-promote. If auto-promotion matters, either increase budget to fit the iteration target or lower `--max-iterations` to fit the budget.
135
+ Metric-call and full-eval budgets are checked only between iterations. Once an iteration starts, `run-gepa` completes the parent minibatch, child minibatch, and full validation eval for an accepted child even if that overshoots the nominal budget. If a run ends as `budget_exhausted`, it pauses and does not auto-promote. If auto-promotion matters, choose a single budget control large enough to cover the intended search.
136
136
 
137
137
  ### Reflection Signal Quality
138
138
 
@@ -62,6 +62,7 @@ Reflection output contract:
62
62
  - The default reflection prompt asks for only the complete updated prompt body. Do not ask the model to wrap the candidate in markdown fences or tags; real prompts often contain those characters.
63
63
  - Put provider-native reasoning controls in `--reflection-provider-settings <json|@file>`, not in the prompt body. For OpenAI reasoning models, use a shape such as `{"reasoning":{"effort":"medium","summary":"auto"}}`. For Anthropic Claude models with thinking controls, use a shape such as `{"thinking":{"type":"adaptive","display":"omitted"},"output_config":{"effort":"medium"}}`.
64
64
  - Reflection max-token limits are explicit. `--reflection-max-tokens <n>` maps to Anthropic `max_tokens` and OpenAI `max_output_tokens`; Anthropic native Messages reflection requires it, while OpenAI may omit it when no cap is desired.
65
+ - Reflection HTTP calls retry transient failures by default (`--reflection-retry-attempts 3`, `--reflection-http-timeout-seconds 180`). Exhausted retryable failures emit `reflection_failed`, consume one candidate-proposal budget slot, and move to the next iteration instead of killing the run.
65
66
 
66
67
  Full command syntax and event contracts: `prompt-control-plane.md`.
67
68
 
@@ -332,9 +332,21 @@ List:
332
332
 
333
333
  ```bash
334
334
  orizu --local prompts list --project <team>/<project>
335
+ orizu --local prompts list --project <team>/<project> --status archived
336
+ orizu --local prompts list --project <team>/<project> --status all
335
337
  orizu --local judges list --project <team>/<project>
336
338
  ```
337
339
 
340
+ Prompt and judge lists show active artifacts by default. Use `--status archived`
341
+ or `--status all` when you need archived artifacts.
342
+
343
+ Archive or restore a prompt:
344
+
345
+ ```bash
346
+ orizu --local prompts archive <prompt-id-or-name> --project <team>/<project>
347
+ orizu --local prompts restore <prompt-id-or-name> --project <team>/<project>
348
+ ```
349
+
338
350
  List prompt comment threads for the latest version, or a specific label/version:
339
351
 
340
352
  ```bash
@@ -607,20 +619,19 @@ orizu --local optimizations run-gepa \
607
619
  --train-split train \
608
620
  --val-split validation \
609
621
  --log-dir ./logs \
610
- --budget light \
611
- --max-iterations 3 \
612
622
  --minibatch-size 3 \
613
623
  --num-threads auto
614
624
  ```
615
625
 
616
626
  Useful GEPA flags:
617
627
 
618
- - `--budget auto|light|medium|heavy`, `--max-metric-calls <n>`, `--max-full-evals <n>`.
628
+ - Budget controls are mutually exclusive: choose at most one of `--budget auto|light|medium|heavy`, `--max-metric-calls <n>`, `--max-full-evals <n>`, or `--max-iterations <n>`. With none provided, `run-gepa` defaults to `--budget auto`, the balanced medium preset.
619
629
  - `--minibatch-size <n>` defaults to 3.
620
- - `--num-threads auto|N` defaults to `auto`; auto caps row-evaluation concurrency from mini-batch size, validation-set size, CPU count, memory estimate, file-descriptor limit, and an 8-thread hard cap.
630
+ - `--num-threads auto|N` defaults to `auto`; auto caps row-evaluation concurrency from mini-batch size, validation-set size, 2x CPU count, memory estimate, file-descriptor limit, and a 64-thread default ceiling. Set `ORIZU_GEPA_AUTO_THREADS_MAX` or use `--num-threads <n>` only when the runner/provider capacity is known.
621
631
  - `--candidate-selection-strategy pareto|current_best|epsilon_greedy`; default is `pareto`.
622
632
  - `--reflection-model <provider/model>`, `--reflection-temperature <n>`, `--reflection-prompt-template <text|@file>`.
623
633
  - `--reflection-max-tokens <n>` is explicit provider config, not a global default. It maps to Anthropic `max_tokens` and OpenAI `max_output_tokens`; Anthropic native Messages reflection requires it, while OpenAI can omit it unless the user wants a cap.
634
+ - `--reflection-retry-attempts` and `--reflection-http-timeout-seconds` tune transient reflection-provider retries. Exhausted retryable failures log `reflection_failed`, count against candidate-proposal budget, and continue with the next iteration.
624
635
  - `--reflection-provider-settings <json|@file>` passes provider-native reflection settings separately from the prompt text. Anthropic example: `{"thinking":{"type":"adaptive","display":"omitted"},"output_config":{"effort":"medium"}}`. OpenAI example: `{"reasoning":{"effort":"medium","summary":"auto"}}`.
625
636
  - `--disable-evaluation-cache` turns off candidate/row/scorer cache reuse.
626
637
  - `--auto-promote --promotion-label <label>` promotes the best candidate at the end.
@@ -718,7 +729,8 @@ Rules:
718
729
  - `eventId` must be stable and unique within one optimization run.
719
730
  - `eventLayer` is `core`, `extension`, or `system`.
720
731
  - Generic core events: `run_started`, `iteration_started`, `candidate_proposed`, `candidate_scored`, `candidate_recommended`, `iteration_completed`, `run_completed`, `run_failed`.
721
- - GEPA extension events: `seed_val_set_started`, `seed_val_set_completed`, `parent_minibatch_started`, `parent_minibatch_completed`, `reflection_started`, `reflection_completed`, `child_candidate_created`, `child_minibatch_started`, `child_minibatch_completed`, `acceptance_decision_made`, `merge_started`, `merge_completed`.
732
+ - GEPA extension events: `seed_val_set_started`, `seed_val_set_completed`, `parent_minibatch_started`, `parent_minibatch_completed`, `reflection_started`, `reflection_completed`, `child_candidate_created`, `child_minibatch_started`, `child_minibatch_completed`, `acceptance_decision_made`, `optimization_progress`, `merge_started`, `merge_completed`.
733
+ - Emit `optimization_progress` after each completed iteration with `percent`, `metric_calls_used`, `metric_call_budget`, and `metric_calls_remaining`. Cap displayed `percent` at `100` and floor `metric_calls_remaining` at `0`; a started iteration may spend past the nominal metric-call budget before the optimizer pauses.
722
734
  - Do not send per-LM-call telemetry to this endpoint in Phase 0. Put aggregate call/token/cost stats in iteration or run payloads.
723
735
 
724
736
  Shell fallback: