diff --git a/pyproject.toml b/pyproject.toml
index a44d0c6..312b641 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,13 +13,15 @@ dependencies = [
"kubernetes>=35.0.0",
"marimo>=0.23.6",
"moutils>=0.3.12",
+ "openai>=2.46.0",
"ray>=2.53.0",
"ruamel-yaml>=0.19.1",
"statistics>=1.0.3.5",
"torch>=2.10.0",
"transformers>=5.0.0",
"typing-extensions>=4.15.0",
- "wandb>=0.24.2",
+ "wandb[sandbox]>=0.28.1",
+ "weave>=0.53.2",
]
[dependency-groups]
diff --git a/sandboxes/README.md b/sandboxes/README.md
new file mode 100644
index 0000000..da451c7
--- /dev/null
+++ b/sandboxes/README.md
@@ -0,0 +1,65 @@
+# CoreWeave Sandboxes Examples
+
+This directory contains marimo notebooks and scripts demonstrating different use cases for [CoreWeave Sandboxes](https://docs.coreweave.com/products/sandboxes), isolated, on-demand execution environments for agentic workloads.
+
+## Notebooks
+
+### 1. [`serverless-sandboxes-tutorial.py`](./serverless-sandboxes-tutorial.py)
+
+An end-to-end code-evaluation workflow: a hosted model (via Serverless Inference's OpenAI-compatible API) generates Python for benchmark tasks, the generated code runs against deterministic tests inside a Serverless Sandbox, and every step is traced and scored in W&B Weave for side-by-side model comparison.
+
+**Use Case:** Evaluating hosted code-generation models with safe, isolated code execution.
+
+### 2. [`harness-evals.py`](./harness-evals.py)
+
+Evaluates full coding-agent CLIs (Codex, Claude Code, OpenClaw, Nous Hermes) that each live inside their own sandbox. Solutions are scored in a separate, network-isolated sandbox against demo tasks or the HumanEval / MBPP benchmarks, with per-agent `weave.Evaluation` runs.
+
+**Use Case:** Benchmarking and comparing agent harnesses on coding tasks at scale.
+
+### 3. [`devin-outpost.py`](./devin-outpost.py)
+
+Creates a Serverless Sandbox from Devin's official CLI image and connects it as
+a single Linux worker for an existing Devin Outpost, with bounded resources and
+explicit cleanup.
+
+**Use Case:** Running Devin sessions inside an isolated, on-demand development
+environment.
+
+### 4. [`claude-remote-control-tutorial.py`](./claude-remote-control-tutorial.py)
+
+A step-by-step, interactive tutorial for turning a Serverless Sandbox into the
+remote machine your Claude Code session runs on. Walks through connecting W&B,
+creating the sandbox (`Sandbox.run()`) with public ingress on port 8080,
+installing Claude Code (`sandbox.exec()`), signing in and launching
+[Remote Control](https://code.claude.com/docs/en/remote-control) over a PTY
+(`sandbox.shell()`), then having Claude build and serve a live website reachable
+at `sandbox.service_address`, and finally cleaning up (`sandbox.stop()`). The
+OAuth login and the `Enable Remote Control?` prompt are handled inline in the
+notebook.
+
+**Use Case:** Steering Claude Code from [claude.ai/code](https://claude.ai/code)
+or the Claude mobile app while execution stays in a cloud sandbox.
+
+## Scripts
+
+### 1. [`claude-remote-control-script.py`](./claude-remote-control-script.py)
+
+The compact, no-frills version of the tutorial above: provisions a sandbox,
+installs Claude Code, pre-trusts `/workspace`, and attaches your local terminal
+to a PTY inside the sandbox so you can sign in and run `claude remote-control`.
+Stops the sandbox on exit.
+
+**Use Case:** Launching a remote Claude Code session from your terminal in one
+command, without the notebook UI.
+
+## Getting Started
+
+1. From the repo root, install dependencies: `uv sync`
+2. Open a notebook in the marimo editor: `uv run marimo edit sandboxes/serverless-sandboxes-tutorial.py`
+3. When prompted about inlined package dependencies, answer `n` to use the project environment (or `Y` for an isolated venv built from the notebook's inline dependencies).
+
+Scripts run directly in the project environment:
+
+```bash
+uv run python sandboxes/claude-remote-control-script.py
+```
diff --git a/sandboxes/assets/image.png b/sandboxes/assets/image.png
new file mode 100644
index 0000000..5174ef4
Binary files /dev/null and b/sandboxes/assets/image.png differ
diff --git a/sandboxes/claude-remote-control-script.py b/sandboxes/claude-remote-control-script.py
new file mode 100644
index 0000000..db81229
--- /dev/null
+++ b/sandboxes/claude-remote-control-script.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+"""Create a sandbox, sign in to Claude Code, and run `claude remote-control`.
+"""
+
+from __future__ import annotations
+
+import fcntl
+import os
+import signal
+import struct
+import sys
+import termios
+import threading
+import tty
+
+os.environ.setdefault("WANDB_SILENT", "true")
+
+from wandb.sandbox import NetworkOptions, ResourceOptions, Sandbox, SandboxDefaults # noqa: E402
+
+# Pre-seeding ~/.claude.json marks /workspace as trusted and skips first-run onboarding
+CLAUDE_JSON = '{"hasCompletedOnboarding": true, "projects": {"/workspace": {"hasTrustDialogAccepted": true}}}'
+BOOTSTRAP = (
+ "npm install -g @anthropic-ai/claude-code --silent "
+ "&& mkdir -p /workspace "
+ f"&& printf '%s' '{CLAUDE_JSON}' > ~/.claude.json"
+)
+# Sign in first (full-scope claude.ai session, stored in the pod's ~/.claude),
+# then serve Remote Control from /workspace.
+RUN = "claude auth login && cd /workspace && exec claude remote-control"
+
+
+def terminal_size() -> tuple[int, int]:
+ try:
+ rows, cols = struct.unpack("hh", fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, b"\0" * 4))
+ return cols or 100, rows or 30
+ except (OSError, struct.error):
+ return 100, 30
+
+
+def attach(sandbox: Sandbox, command: list[str]) -> int:
+ """Bridge the local terminal to a PTY inside the sandbox."""
+ cols, rows = terminal_size()
+ session = sandbox.shell(command, width=cols, height=rows)
+
+ def pump_output() -> None:
+ try:
+ for chunk in session.output:
+ sys.stdout.buffer.write(chunk)
+ sys.stdout.buffer.flush()
+ except Exception: # noqa: BLE001 - session closing is the normal exit path
+ pass
+
+ threading.Thread(target=pump_output, daemon=True).start()
+
+ def on_resize(*_: object) -> None:
+ new_cols, new_rows = terminal_size()
+ try:
+ session.resize(new_cols, new_rows)
+ except Exception: # noqa: BLE001 - resize is cosmetic
+ pass
+
+ signal.signal(signal.SIGWINCH, on_resize)
+
+ fd = sys.stdin.fileno()
+ saved = termios.tcgetattr(fd)
+ try:
+ tty.setraw(fd)
+ while True:
+ data = os.read(fd, 1024)
+ if not data:
+ break
+ try:
+ session.stdin.write(data).result()
+ except Exception: # noqa: BLE001 - remote session ended; stop forwarding
+ break
+ except (OSError, KeyboardInterrupt):
+ pass
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, saved)
+
+ try:
+ return session.wait(timeout=10)
+ except Exception: # noqa: BLE001
+ return 0
+
+
+def main() -> int:
+ print("Creating sandbox...", flush=True)
+ sandbox = Sandbox.run(
+ defaults=SandboxDefaults(
+ container_image="node:22",
+ tags=("claude-code", "remote-control"),
+ resources=ResourceOptions(requests={"cpu": "2", "memory": "4Gi"}),
+ ),
+ network=NetworkOptions(egress_mode="internet"),
+ max_lifetime_seconds=4 * 3600,
+ )
+ sandbox.wait()
+ print(f" {sandbox.sandbox_id} (expires in 4h)", flush=True)
+
+ print("Installing Claude Code...", flush=True)
+ setup = sandbox.exec(["bash", "-lc", BOOTSTRAP], timeout_seconds=900)
+ setup.wait(timeout=900)
+ if setup.returncode != 0:
+ sys.stderr.write(setup.result().stderr_bytes.decode(errors="replace"))
+ sandbox.stop(missing_ok=True).result()
+ return 1
+
+ print("Sign in when prompted, then Remote Control starts. Ctrl-C stops it.\n", flush=True)
+ code = attach(sandbox, ["bash", "-lc", RUN])
+
+ print("\nStopping sandbox...", flush=True)
+ sandbox.stop(missing_ok=True).result()
+ return code
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/sandboxes/claude-remote-control-tutorial.py b/sandboxes/claude-remote-control-tutorial.py
new file mode 100644
index 0000000..53016e0
--- /dev/null
+++ b/sandboxes/claude-remote-control-tutorial.py
@@ -0,0 +1,791 @@
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "anywidget>=0.9",
+# "marimo>=0.23.6",
+# "wandb[sandbox]>=0.28.1",
+# ]
+# ///
+
+import marimo
+
+__generated_with = "0.23.15"
+app = marimo.App(
+ width="medium",
+ app_title="Run Claude Code in a Remote Sandbox Environment",
+ auto_download=["html"],
+)
+
+
+@app.cell
+def _():
+ import os
+ import re
+ import time
+
+ import anywidget
+ import marimo as mo
+ import requests
+
+ os.environ.setdefault("WANDB_SILENT", "true")
+
+ from wandb.sandbox import (
+ NetworkOptions,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ )
+
+ return (
+ NetworkOptions,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ anywidget,
+ mo,
+ os,
+ re,
+ requests,
+ time,
+ )
+
+
+@app.cell
+def _():
+ # Survives relaunches of the sign-in step (this cell has no button
+ # dependency, so it runs once). Holds the previous PTY session and its
+ # state dict so a second "Start sign-in" press can tear the old one down
+ # instead of leaking another `claude` process + pump thread into the
+ # sandbox. That leak made repeated runs flaky.
+ launch_registry: dict = {"session": None, "state": None}
+ return (launch_registry,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ # Run Claude Code in a Remote Sandbox Environment
+
+ /// admonition | What this notebook does
+ type: info
+
+ Claude Code runs inside a CoreWeave **Serverless Sandbox**, and you steer it
+ from [claude.ai/code](https://claude.ai/code) or the Claude mobile app via
+ **Remote Control**. Your laptop is only the interface; execution stays in the
+ sandbox, inference stays on Anthropic's API.
+
+ You need a [claude.ai](https://claude.ai/) subscription
+ (Pro/Max/Team/Enterprise) for the sign-in step.
+ ///
+
+ /// admonition | Why a sandbox instead of the default cloud environment
+ type: note
+
+ Claude Code's built-in cloud environment is a locked-down, repo-only VM. Your
+ own sandbox lets Claude do what that environment can't:
+
+ - **Reach your tools and infrastructure.** Run inside your org's network to
+ hit internal services, private registries, databases, and clusters.
+ - **Use real compute.** GPUs and large CPU or memory for training, inference,
+ or heavy builds.
+ - **Host on a public URL.** Public ingress makes a dev server Claude starts
+ reachable on the internet. The default environment only produces a PR, it
+ can't expose a running site.
+ - **Bring a custom environment.** Your own image, mounted data, and config.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ wandb_key_form = (
+ mo.md("{api_key}")
+ .batch(
+ api_key=mo.ui.text(
+ kind="password",
+ placeholder="W&B API key from wandb.ai/authorize",
+ full_width=True,
+ ),
+ )
+ .form(submit_button_label="Connect", bordered=False)
+ )
+ mo.vstack(
+ [
+ mo.md(r"""
+ ---
+ ## 1. Connect W&B
+
+ Paste your W&B API key from
+ [wandb.ai/authorize](https://wandb.ai/authorize). It authenticates
+ every `Sandbox` call in this notebook, starting with `Sandbox.run()`
+ in the next step.
+ """),
+ wandb_key_form,
+ ]
+ )
+ return (wandb_key_form,)
+
+
+@app.cell(hide_code=True)
+def _(mo, os, requests, wandb_key_form):
+ form_value = wandb_key_form.value or {}
+ candidate_key = form_value.get("api_key", "").strip()
+ mo.stop(not candidate_key, mo.md("_Paste your API key above and press **Connect**._"))
+
+ # Validate against the W&B API before exporting anything. This is the same
+ # check `wandb login --verify` performs, minus its side effect of writing
+ # the (possibly wrong) key to ~/.netrc before verifying it.
+ with mo.status.spinner(title="Validating key with api.wandb.ai..."):
+ viewer_resp = requests.post(
+ "https://api.wandb.ai/graphql",
+ json={"query": "query Viewer { viewer { username } }"},
+ auth=("api", candidate_key),
+ timeout=15,
+ )
+ viewer = (viewer_resp.json().get("data") or {}).get("viewer") if viewer_resp.ok else None
+ mo.stop(
+ not (viewer and viewer.get("username")),
+ mo.callout(
+ mo.md("❌ api.wandb.ai rejected this key. Copy it again from [wandb.ai/authorize](https://wandb.ai/authorize)."),
+ kind="danger",
+ ),
+ )
+
+ WANDB_KEY = candidate_key
+ os.environ["WANDB_API_KEY"] = WANDB_KEY
+ mo.callout(mo.md(f"✅ Key verified. Connected as **{viewer['username']}**."), kind="success")
+ return (WANDB_KEY,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 2. Create the sandbox
+
+ Pick a lifetime and press **Create sandbox** to call
+ `Sandbox.run(..., max_lifetime_seconds=...)`.
+ - The lifetime is a hard cap: it
+ can't be extended later, and expiry kills the sandbox without preserving files.
+ - `NetworkOptions(egress_mode="internet", ingress_mode="public", exposed_ports=(8080,))`
+ gives it internet egress plus public ingress on **port 8080**, so anything
+ Claude serves there is reachable straight from your browser (used in step 6).
+ """)
+ return
+
+
+@app.cell
+def _(ResourceOptions, SandboxDefaults):
+ SANDBOX_DEFAULTS = SandboxDefaults(
+ container_image="node:22",
+ tags=("claude-code", "remote-control", "tutorial"),
+ resources=ResourceOptions(requests={"cpu": "2", "memory": "4Gi"}),
+ )
+ return (SANDBOX_DEFAULTS,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ lifetime_slider = mo.ui.slider(
+ start=1, stop=12, step=1, value=4,
+ label="Sandbox lifetime (hours)",
+ show_value=True,
+ )
+ create_btn = mo.ui.run_button(label="Create sandbox", kind="success")
+ mo.hstack([lifetime_slider, create_btn], justify="start", gap=2)
+ return create_btn, lifetime_slider
+
+
+@app.cell
+def _(
+ NetworkOptions,
+ SANDBOX_DEFAULTS,
+ Sandbox,
+ WANDB_KEY,
+ create_btn,
+ lifetime_slider,
+ mo,
+):
+ assert WANDB_KEY # step 1 must be done first
+ mo.stop(not create_btn.value, mo.md("_Press **Create sandbox** to provision the sandbox._"))
+
+ lifetime_hours = lifetime_slider.value
+ with mo.status.spinner(title="Creating sandbox..."):
+ sandbox = Sandbox.run(
+ defaults=SANDBOX_DEFAULTS,
+ network=NetworkOptions(
+ egress_mode="internet",
+ ingress_mode="public",
+ exposed_ports=(8080,),
+ ),
+ max_lifetime_seconds=int(lifetime_hours * 3600),
+ )
+ sandbox.wait()
+
+ service_note = (
+ f" Port 8080 is public at **`{sandbox.service_address}`**."
+ if sandbox.service_address
+ else ""
+ )
+ mo.callout(
+ mo.md(
+ f"✅ Sandbox **`{sandbox.sandbox_id}`** is running, hard expiry in "
+ f"**{lifetime_hours}h**.{service_note}"
+ ),
+ kind="success",
+ )
+ return (sandbox,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 3. Install Claude Code
+
+ `sandbox.exec(...)` runs one command that installs Claude Code via npm and
+ pre-writes `~/.claude.json` so `/workspace` is already trusted.
+ """)
+ return
+
+
+@app.cell
+def _(mo, sandbox):
+ CLAUDE_JSON = '{"hasCompletedOnboarding": true, "projects": {"/workspace": {"hasTrustDialogAccepted": true}}}'
+ BOOTSTRAP_CMD = (
+ "npm install -g @anthropic-ai/claude-code --silent "
+ "&& mkdir -p /workspace "
+ f"&& printf '%s' '{CLAUDE_JSON}' > ~/.claude.json"
+ )
+
+ with mo.status.spinner(title="Installing Claude Code in the sandbox (~30-60s)..."):
+ bootstrap_proc = sandbox.exec(["bash", "-lc", BOOTSTRAP_CMD], timeout_seconds=900)
+ bootstrap_proc.wait(timeout=900)
+
+ bootstrap_ok = bootstrap_proc.returncode == 0
+ if bootstrap_ok:
+ bootstrap_note = mo.callout(mo.md("✅ Claude Code installed and `/workspace` pre-trusted."), kind="success")
+ else:
+ bootstrap_stderr = bootstrap_proc.result().stderr_bytes.decode(errors="replace")
+ bootstrap_note = mo.callout(
+ mo.md(f"❌ Bootstrap failed:\n\n```\n{bootstrap_stderr[-1500:]}\n```"),
+ kind="danger",
+ )
+ bootstrap_note
+ return (bootstrap_ok,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 4. Sign in and start Remote Control
+
+ Remote Control only accepts a full claude.ai login (API keys and setup-tokens
+ are rejected), so `sandbox.shell(...)` opens a PTY running
+ `claude auth login && cd /workspace && exec claude remote-control --name
+ 'CoreWeave Sandboxes'`. The `--name` flag is what titles the session in
+ claude.ai/code; change that string to rename it. Only two steps need you:
+
+ 1. Open the **authorization link** when it appears in the banner and approve.
+ 2. Paste the returned code into the box and press **Submit code**.
+
+ When the banner turns green it pins the **session link** and the Remote
+ Control details right there in the panel, so they stay put instead of
+ scrolling away. Step 5 explains exactly what to do with them.
+
+ The panel refreshes itself every couple of seconds during sign-in and stops
+ once Remote Control is up. If something gets stuck, the raw sandbox console and
+ manual keys are in the collapsible section at the bottom. Pressing **Start
+ sign-in + Remote Control** again cleanly restarts the session (it stops the
+ previous one first).
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(anywidget, mo):
+ class NotificationSilencer(anywidget.AnyWidget):
+ """Invisible widget that stubs the browser Notification API.
+
+ marimo fires a desktop notification every time a run completes while
+ the tab is unfocused, and the auto-refresh console below completes a
+ run every couple of seconds. marimo has no setting to turn this off
+ (v0.23), but its code bails out when Notification.permission is
+ "denied", so this widget replaces window.Notification with a stub
+ that always reports "denied". Applies to this notebook page only.
+ """
+
+ _esm = """
+ function render({ el }) {
+ class SilentNotification {
+ static get permission() { return "denied"; }
+ static requestPermission() { return Promise.resolve("denied"); }
+ }
+ window.Notification = SilentNotification;
+ el.style.display = "none";
+ }
+ export default { render };
+ """
+
+ notification_silencer = mo.ui.anywidget(NotificationSilencer())
+ notification_silencer
+ return
+
+
+@app.cell(hide_code=True)
+def _(bootstrap_ok, mo):
+ mo.stop(not bootstrap_ok, mo.md("_Fix the bootstrap above first._"))
+ launch_btn = mo.ui.run_button(label="Start sign-in + Remote Control", kind="success")
+ launch_btn
+ return (launch_btn,)
+
+
+@app.cell(hide_code=True)
+def _(launch_btn, launch_registry, mo, re, sandbox):
+ mo.stop(not launch_btn.value, mo.md("_Press the button to open the PTY session in the sandbox._"))
+
+ # Relaunch cleanup: stop the previous session's Remote Control server and
+ # signal its pump thread to exit, so we never run two `claude` PTYs at once.
+ prev_session = launch_registry.get("session")
+ prev_state = launch_registry.get("state")
+ if prev_session is not None:
+ if prev_state is not None:
+ prev_state["ended"] = True
+ try:
+ prev_session.stdin.write(b"\x03").result() # Ctrl-C the old claude
+ except Exception: # noqa: BLE001 - old PTY may already be gone
+ pass
+
+ RUN_CMD = (
+ "claude auth login && cd /workspace "
+ "&& exec claude remote-control --name 'CoreWeave Sandboxes'"
+ )
+ output_chunks: list[bytes] = []
+ # Holds the last response to a submitted code so the callout survives
+ # panel re-renders.
+ feedback_store: dict = {}
+ # Flipped by the pump thread when the PTY closes (e.g. after Ctrl-C), so
+ # the panel stops writing to a dead stream.
+ session_state = {"ended": False}
+ # A very wide PTY keeps long OAuth / session URLs on a single line.
+ session = sandbox.shell(["bash", "-lc", RUN_CMD], width=500, height=40)
+
+ ANSI_RE = re.compile(
+ rb"\x1b\[[0-9;?]*[ -/]*[@-~]" # CSI sequences (colors, cursor movement)
+ rb"|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)" # OSC sequences (titles, links)
+ rb"|\x1b[@-_]" # other escapes
+ )
+
+ def render_output(chunks: list[bytes], max_lines: int = 40) -> str:
+ text = ANSI_RE.sub(b"", b"".join(chunks)).decode("utf-8", errors="replace")
+ lines = []
+ for raw_line in text.split("\n"):
+ # PTYs end lines with \r\n: drop the trailing \r, then keep only
+ # the text after any remaining \r (in-place TUI redraws).
+ line = raw_line.rstrip("\r")
+ lines.append(line.rsplit("\r", 1)[-1])
+ return "\n".join(lines[-max_lines:])
+
+ def find_urls(chunks: list[bytes]) -> list[str]:
+ # Scan the RAW stream: links can arrive inside OSC-8 hyperlink escapes,
+ # which ANSI stripping would delete.
+ raw_text = b"".join(chunks).decode("utf-8", errors="replace")
+ urls: list[str] = []
+ for raw_url in re.findall(r"https://[^\s\x1b\x07\"'`]+", raw_text):
+ url = raw_url.rstrip(").,;|>")
+ if url not in urls and ("claude.ai" in url or "claude.com" in url):
+ urls.append(url)
+ return urls
+
+ def pump_session_output() -> None:
+ # Both interactive prompts in this flow have fixed answers, so the pump
+ # answers them itself: Enter picks the (pre-selected) claude.ai option
+ # at the login menu, and "y" confirms the "Enable Remote Control?"
+ # prompt. The only human steps left are opening the authorization link
+ # and pasting the code back.
+ thread = mo.current_thread()
+ auto_answered = {"login_menu": False, "enable_rc": False}
+ try:
+ for chunk in session.output:
+ # marimo sets should_exit when this cell is re-run, interrupted,
+ # or the kernel is restarted. Bail so the thread doesn't outlive
+ # its session and desync the frontend. (The relaunch teardown
+ # above Ctrl-Cs the old PTY, which unblocks this read so the
+ # check is reached promptly.)
+ if thread.should_exit:
+ break
+ output_chunks.append(chunk)
+ text = b"".join(output_chunks).decode("utf-8", errors="replace")
+ if not auto_answered["login_menu"] and "login method" in text.lower():
+ auto_answered["login_menu"] = True
+ session.stdin.write(b"\r").result()
+ if not auto_answered["enable_rc"] and "Enable Remote Control?" in text:
+ auto_answered["enable_rc"] = True
+ session.stdin.write(b"y\r").result()
+ except Exception: # noqa: BLE001 - session closing is the normal exit path
+ pass
+ finally:
+ session_state["ended"] = True
+
+ # mo.Thread, not threading.Thread: marimo tracks it across re-runs and kernel
+ # restarts and signals should_exit on invalidation. A raw thread survives a
+ # restart orphaned, which is what left the panel broken until a full page
+ # reload (Cmd+R). Requires marimo >= 0.23.
+ mo.Thread(target=pump_session_output, daemon=True).start()
+ launch_registry["session"] = session
+ launch_registry["state"] = session_state
+ return (
+ feedback_store,
+ find_urls,
+ output_chunks,
+ render_output,
+ session,
+ session_state,
+ )
+
+
+@app.cell(hide_code=True)
+def _(mo, session):
+ assert session is not None # console pairs with the live session
+ # Rendered invisibly by the panel below, and only until sign-in completes.
+ console_refresh = mo.ui.refresh(default_interval="2s")
+ key_input = mo.ui.text(placeholder="paste the authorization code here", full_width=True)
+ send_btn = mo.ui.run_button(label="Submit code", kind="success")
+ enter_btn = mo.ui.run_button(label="Enter")
+ up_btn = mo.ui.run_button(label="↑")
+ down_btn = mo.ui.run_button(label="↓")
+ ctrl_c_btn = mo.ui.run_button(label="Ctrl-C", kind="danger")
+ return (
+ console_refresh,
+ ctrl_c_btn,
+ down_btn,
+ enter_btn,
+ key_input,
+ send_btn,
+ up_btn,
+ )
+
+
+@app.cell(hide_code=True)
+def _(
+ console_refresh,
+ ctrl_c_btn,
+ down_btn,
+ enter_btn,
+ feedback_store: dict,
+ find_urls,
+ key_input,
+ mo,
+ output_chunks: list[bytes],
+ render_output,
+ send_btn,
+ session,
+ session_state,
+ time,
+ up_btn,
+):
+ console_refresh.value # re-render on each invisible tick (while sign-in runs)
+
+ keystrokes = b""
+ if send_btn.value:
+ keystrokes = key_input.value.encode() + b"\r"
+ elif enter_btn.value:
+ keystrokes = b"\r"
+ elif up_btn.value:
+ keystrokes = b"\x1b[A"
+ elif down_btn.value:
+ keystrokes = b"\x1b[B"
+ elif ctrl_c_btn.value:
+ keystrokes = b"\x03"
+
+ if keystrokes and not session_state["ended"]:
+ chunk_mark = len(output_chunks)
+ try:
+ session.stdin.write(keystrokes).result()
+ except Exception: # noqa: BLE001 - PTY closed between check and write
+ session_state["ended"] = True
+ if send_btn.value and not session_state["ended"]:
+ # Wait for the sandbox to process the pasted code, then persist its
+ # full response, success or error, so the callout survives
+ # later re-renders.
+ with mo.status.spinner(title="Submitting code to the sandbox..."):
+ time.sleep(6)
+ response_text = render_output(output_chunks[chunk_mark:], max_lines=200).strip()
+ response_lower = response_text.lower()
+ has_error = any(w in response_lower for w in ("error", "invalid", "failed", "expired", "denied"))
+ has_success = any(w in response_lower for w in ("success", "logged in", "welcome"))
+ feedback_store["kind"] = "danger" if has_error else ("success" if has_success else "info")
+ feedback_store["text"] = response_text or "(no output captured, check the console below)"
+
+ console_text = render_output(output_chunks) or "(waiting for output...)"
+ full_text = b"".join(output_chunks).decode("utf-8", errors="replace")
+ detected_urls = find_urls(output_chunks)
+ authorize_url = next((u for u in detected_urls if "oauth" in u or "authorize" in u), None)
+ rc_session_url = next((u for u in detected_urls if "/code/" in u), None)
+ rc_policy_blocked = "Remote Control is disabled" in full_text
+ signed_in = "Login successful" in full_text
+ session_ended = session_state["ended"]
+
+ # Once the session URL appears, keep refreshing a few more ticks so the rest
+ # of the Remote Control banner (the "how to connect" instructions) finishes
+ # printing, then snapshot it into feedback_store. Stored there, it survives
+ # every later re-render instead of flashing once and vanishing.
+ if rc_session_url:
+ feedback_store["rc_ticks"] = feedback_store.get("rc_ticks", 0) + 1
+ feedback_store["remote_control_output"] = render_output(output_chunks, max_lines=200).strip()
+ rc_settled = feedback_store.get("rc_ticks", 0) >= 3
+ flow_done = (bool(rc_session_url) and rc_settled) or rc_policy_blocked or session_ended
+
+ if session_ended:
+ status = mo.callout(
+ mo.md(
+ "⚪ **The sandbox session has ended** (Remote Control stopped). Press "
+ "**Start sign-in + Remote Control** above to relaunch, or continue to **Clean up**."
+ ),
+ kind="warn",
+ )
+ elif rc_session_url:
+ status = mo.callout(
+ mo.md(
+ f"🟢 **Remote Control is live.** [Open your session ↗]({rc_session_url}), "
+ f"or find it under **Code** in the Claude mobile app."
+ ),
+ kind="success",
+ )
+ elif rc_policy_blocked:
+ status = mo.callout(
+ mo.md(
+ "❌ **Signed in, but your organization has Remote Control disabled.** "
+ "On Team and Enterprise plans it's off by default, an Owner must enable the "
+ "**Remote Control** toggle at "
+ "[claude.ai/admin-settings/claude-code](https://claude.ai/admin-settings/claude-code), "
+ "then restart this step."
+ ),
+ kind="danger",
+ )
+ elif signed_in:
+ status = mo.callout(
+ mo.md("✅ **Signed in.** Enabling Remote Control automatically, the session link will appear here in a moment..."),
+ kind="info",
+ )
+ elif authorize_url:
+ status = mo.callout(
+ mo.md(
+ f"🔑 **Sign in:** [open the authorization page ↗]({authorize_url}), approve, "
+ f"then paste the returned code below and press **Submit code**."
+ ),
+ kind="info",
+ )
+ else:
+ status = mo.callout(
+ mo.md("⏳ Starting sign-in. The login menu is answered automatically; the authorization link will appear here."),
+ kind="neutral",
+ )
+
+ panel_items = [status]
+ if feedback_store.get("remote_control_output"):
+ # Persisted so the "how to connect" text stays put after the panel
+ # freezes. Previously it flashed once and was gone.
+ panel_items.append(
+ mo.callout(
+ mo.vstack(
+ [
+ mo.md("**Remote Control session details (kept here for reference):**"),
+ mo.plain_text(feedback_store["remote_control_output"]),
+ ]
+ ),
+ kind="success",
+ )
+ )
+ if feedback_store.get("text"):
+ panel_items.append(
+ mo.callout(
+ mo.vstack(
+ [
+ mo.md("**Sandbox response to the submitted code:**"),
+ mo.plain_text(feedback_store["text"]),
+ ]
+ ),
+ kind=feedback_store["kind"],
+ )
+ )
+ if authorize_url and not signed_in and not flow_done:
+ # The code box only matters between "link surfaced" and "signed in".
+ panel_items.append(mo.hstack([key_input, send_btn], widths=[5, 1], gap=0.5))
+ panel_items.append(
+ mo.accordion(
+ {
+ "Raw sandbox console + manual keys": mo.vstack(
+ [
+ mo.plain_text(console_text),
+ mo.hstack([enter_btn, up_btn, down_btn, ctrl_c_btn], justify="start", gap=0.5),
+ ]
+ )
+ }
+ )
+ )
+ if not flow_done:
+ # The invisible refresh only renders (and therefore only ticks) while
+ # sign-in is still in progress; once Remote Control is live or blocked,
+ # it drops out of the output and the auto-refresh stops for good.
+ panel_items.append(mo.Html(f"
{console_refresh}
"))
+ mo.vstack(panel_items)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 5. Use your new remote environment
+
+ With the banner green, the sandbox is now hosting a **Remote Control session**
+ registered to your claude.ai account. It appears anywhere you're signed in,
+ marked with a computer icon and a green status dot. The sandbox does the work;
+ every surface below is just a window into it. You can drive it from all three
+ at once, messages, subagent progress, and files stay in sync.
+
+ ### From the web (claude.ai/code)
+
+ 1. Click the **session link** in the green banner above, or open
+ [claude.ai/code](https://claude.ai/code) and pick it out of the list by
+ name. It shows up as **CoreWeave Sandboxes** (set with `--name` on the
+ `claude remote-control` command in step 4; change that string to rename it,
+ or run `/rename` in the session). Online sessions show a computer icon with
+ a green dot.
+ 2. Type a prompt. It runs **inside the sandbox**, against the sandbox
+ filesystem, and `@` autocompletes paths from `/workspace`.
+
+ ### From your phone (the Claude app)
+
+ 1. Install the Claude app for [iOS](https://apps.apple.com/us/app/claude-by-anthropic/id6473753684)
+ or [Android](https://play.google.com/store/apps/details?id=com.anthropic.claude)
+ and sign in with the **same** account.
+ 2. Tap **Code** in the bottom navigation to reach the session list, then open
+ the session with the green dot. (No app yet? Run `/mobile` in a terminal
+ Claude Code session for a download QR code.)
+ 3. Approve tool calls and send follow-ups from anywhere. Ask "notify me when
+ the build finishes" and a long turn will push to your phone.
+
+ This notebook already started the host process for you: inside the sandbox it
+ ran `claude remote-control` (server mode), which is what registered the
+ session. There is nothing extra to run to use *this* sandbox, connect from the
+ web or phone above.
+
+ /// admonition | `/teleport` runs on *your* machine, not the sandbox
+ type: warning
+
+ claude.ai/code offers an **Open in terminal** button that copies a
+ `claude --teleport ` command. Running it does **not** attach your
+ terminal to the sandbox, it forks the conversation into a **new local session
+ on your laptop**, seeded with a copy of the transcript. Execution and
+ filesystem then belong to your laptop (ask for `hostname` and you'll see your
+ laptop, while claude.ai/code still reports the sandbox). The two sessions
+ diverge from that point, local work won't appear in the app. Only the
+ transcript travels; the host machine never does. To keep working *in the
+ sandbox*, steer it from claude.ai/code or mobile, or open another
+ `sandbox.shell(...)` into it, don't teleport.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 6. Try it: have Claude build a live website
+
+ Remote Control is running, so switch to [claude.ai/code](https://claude.ai/code)
+ (or the **Code** tab in the mobile app), open your session, and paste the
+ prompt below, it already contains this sandbox's public address (read from
+ `sandbox.service_address`). Claude will build the site inside the sandbox and
+ serve it on port 8080; open the link when it reports done.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo, sandbox, session):
+ assert session is not None # meaningful only once Remote Control is running
+
+ if sandbox.service_address:
+ site_url = f"http://{sandbox.service_address}"
+ demo_prompt = (
+ "Build me a sample website and give me a link to access it. Make it a \n"
+ "landing page for the concept of running a W&B Serverless Sandbox as a \n"
+ "remote environment for Claude Code. Add some cool design elements and \n"
+ "animations, and make sure actual logos are present for both Weights & Biases \n"
+ "and Claude. Serve it on port 8080, bound to 0.0.0.0, and keep the \n"
+ "server running. Port 8080 on this machine is publicly reachable at \n"
+ f"{site_url}. Once the server is up, give me that direct link to \n"
+ "access the site."
+ )
+ demo_out = mo.vstack(
+ [
+ mo.md(f"```text\n{demo_prompt}\n```"),
+ mo.md(f"Once Claude reports the server is running, the site is at [{site_url}]({site_url})."),
+ ]
+ )
+ else:
+ demo_out = mo.callout(
+ mo.md(
+ "⚠️ This sandbox has no public service address, the runner may not "
+ "support `ingress_mode=\"public\"`. Recreate the sandbox in step 2 "
+ "or ask your W&B admin about ingress support."
+ ),
+ kind="warn",
+ )
+ demo_out
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 7. Clean up
+
+ The sandbox bills until you stop it or the lifetime expires. Ctrl-C above stops the Remote Control server; the button below calls
+ `sandbox.stop()`. Lost sandboxes: `Sandbox.list(tags=["remote-control"]).result()`.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo, sandbox):
+ assert sandbox is not None # nothing to stop before creation
+ stop_btn = mo.ui.run_button(label="Stop sandbox", kind="danger")
+ stop_btn
+ return (stop_btn,)
+
+
+@app.cell
+def _(mo, sandbox, stop_btn):
+ mo.stop(not stop_btn.value, mo.md("_Press **Stop sandbox** when you're finished._"))
+ sandbox.stop(missing_ok=True).result()
+ mo.callout(mo.md(f"✅ Sandbox `{sandbox.sandbox_id}` stopped."), kind="success")
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ /// details | Where to next
+ type: info
+
+ - [`claude-remote-control-script.py`](https://github.com/coreweave/reference-architecture/blob/main/sandboxes/claude-remote-control-script.py):
+ the compact terminal version of this notebook — attach your real terminal
+ to a PTY in the sandbox and sign in from there
+ - [Remote Control docs](https://code.claude.com/docs/en/remote-control)
+ - [Sandbox environments compared](https://code.claude.com/docs/en/sandbox-environments)
+ ///
+ """)
+ return
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/sandboxes/devin-outpost.py b/sandboxes/devin-outpost.py
new file mode 100644
index 0000000..a06bfec
--- /dev/null
+++ b/sandboxes/devin-outpost.py
@@ -0,0 +1,332 @@
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+# "marimo>=0.23.6",
+# "wandb[sandbox]>=0.28.1",
+# ]
+# ///
+
+import marimo
+
+__generated_with = "0.23.14"
+app = marimo.App(
+ width="medium",
+ app_title="Devin Outpost on CW Serverless Sandboxes",
+ css_file="/usr/local/_marimo/custom.css",
+ auto_download=["html"],
+)
+
+
+@app.cell
+def _():
+ import os
+ import time
+ from pathlib import Path
+
+ import marimo as mo
+ from wandb.sandbox import (
+ NetworkOptions,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ )
+
+ return (
+ NetworkOptions,
+ Path,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ mo,
+ os,
+ time,
+ )
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.vstack(
+ [
+ mo.md(
+ r"""
+ # Run a Devin Outpost on CW Serverless Sandboxes
+
+ /// admonition | About This Notebook
+ type: info
+
+ This tutorial connects a single
+ [CW Serverless Sandbox](https://docs.wandb.ai/sandboxes)
+ to a Devin Outpost. You create the Linux Outpost in Devin Cloud,
+ paste the token shown at creation, and launch an isolated worker
+ from Devin's official CLI image.
+
+ _If you are running this notebook in edit mode, start by running all cells._
+ ///
+ """
+ ),
+ mo.md(
+ r"""
+ /// details | Table of Contents
+ type: info
+
+ - [**Create a Devin Outpost**](#1-create-a-devin-outpost)
+ - [**Connect credentials**](#2-connect-credentials)
+ - [**Start the worker**](#3-start-the-worker)
+ - [**Start a Devin session**](#4-start-a-devin-session)
+ - [**Clean up**](#5-clean-up)
+ ///
+ """
+ ),
+ ]
+ )
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 1. Create a Devin Outpost
+
+ In [Devin Cloud](https://app.devin.ai), open
+ **Settings → Environment → Outposts**, select **Create Outpost**, give it
+ a name, and choose **Linux** as the platform.
+
+ Devin shows the Outpost token once. Copy it and keep it secure; the
+ notebook asks for it in the next section. For the authoritative setup
+ steps and current prerequisites, see the
+ [Devin Outposts quickstart](https://docs.devin.ai/cloud/outposts/quickstart).
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 2. Connect credentials
+
+ Enter the W&B key used to create CW Serverless Sandboxes, plus the Outpost
+ name and token shown when you created it. Both secrets are masked, and
+ the Outpost token is passed to the worker through an environment variable
+ instead of being embedded in its command text.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ credentials_form = (
+ mo.md(
+ """
+ - W&B API key: {wandb_api_key}
+ - Devin Outpost name: {outpost_name}
+ - Devin Outpost token: {outpost_token}
+ """
+ )
+ .batch(
+ wandb_api_key=mo.ui.text(
+ kind="password",
+ placeholder="from wandb.ai/authorize",
+ full_width=True,
+ ),
+ outpost_name=mo.ui.text(
+ placeholder="my-linux-outpost",
+ full_width=True,
+ ),
+ outpost_token=mo.ui.text(
+ kind="password",
+ placeholder="shown once when the Outpost is created",
+ full_width=True,
+ ),
+ )
+ .form(submit_button_label="Launch worker", bordered=True)
+ )
+ credentials_form
+ return (credentials_form,)
+
+
+@app.cell(hide_code=True)
+def _(credentials_form, mo, os):
+ credentials = credentials_form.value or {}
+ WANDB_API_KEY = credentials.get("wandb_api_key")
+ DEVIN_OUTPOST_NAME = credentials.get("outpost_name")
+ DEVIN_OUTPOST_TOKEN = credentials.get("outpost_token")
+
+ mo.stop(
+ not (WANDB_API_KEY and DEVIN_OUTPOST_NAME and DEVIN_OUTPOST_TOKEN),
+ mo.md("_Fill in all three fields and press **Launch worker**._"),
+ )
+
+ os.environ["WANDB_API_KEY"] = WANDB_API_KEY
+ return DEVIN_OUTPOST_NAME, DEVIN_OUTPOST_TOKEN
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 3. Start the worker
+
+ The sandbox uses Devin's official CLI image and installs `git`, the
+ required developer tool from Devin's container quickstart. Internet
+ egress lets the worker reach Devin and external package registries.
+
+ This example keeps one worker alive for at most one hour. For repeated
+ use, bake your repositories and development tools into a dedicated
+ image instead of installing them at startup.
+ """)
+ return
+
+
+@app.cell
+def _(
+ DEVIN_OUTPOST_NAME,
+ DEVIN_OUTPOST_TOKEN,
+ NetworkOptions,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ mo,
+ time,
+):
+ defaults = SandboxDefaults(
+ container_image="public.ecr.aws/e0h8a4b6/devin-cli:stable",
+ tags=("devin-outpost", "tutorial"),
+ environment_variables={
+ "DEVIN_OUTPOST_NAME": DEVIN_OUTPOST_NAME,
+ "DEVIN_OUTPOST_TOKEN": DEVIN_OUTPOST_TOKEN,
+ },
+ resources=ResourceOptions(
+ requests={"cpu": "1", "memory": "2Gi"},
+ limits={"cpu": "2", "memory": "4Gi"},
+ ),
+ )
+
+ sandbox = Sandbox.run(
+ defaults=defaults,
+ network=NetworkOptions(egress_mode="internet"),
+ max_lifetime_seconds=3600,
+ )
+
+ setup = sandbox.exec(
+ [
+ "bash",
+ "-lc",
+ (
+ "apt-get update && "
+ "apt-get install -y --no-install-recommends ca-certificates git && "
+ "rm -rf /var/lib/apt/lists/* && "
+ "mkdir -p /repos"
+ ),
+ ],
+ timeout_seconds=300,
+ ).result()
+ mo.stop(
+ setup.returncode != 0,
+ mo.callout(
+ mo.md(f"Worker setup failed with exit code `{setup.returncode}`."),
+ kind="danger",
+ ),
+ )
+
+ worker_process = sandbox.exec(
+ [
+ "bash",
+ "-lc",
+ (
+ 'exec devin worker start --outpost="$DEVIN_OUTPOST_NAME" '
+ '--token="$DEVIN_OUTPOST_TOKEN"'
+ ),
+ ],
+ cwd="/repos",
+ )
+ time.sleep(2)
+ worker_returncode = worker_process.poll()
+ mo.stop(
+ worker_returncode is not None,
+ mo.callout(
+ mo.md(
+ f"Devin worker exited during startup with code `{worker_returncode}`."
+ ),
+ kind="danger",
+ ),
+ )
+
+ mo.callout(
+ mo.md(
+ f"🟢 Worker launched in sandbox `{sandbox.sandbox_id}`. "
+ "In Devin Cloud, start a session and choose this Outpost under "
+ "**Configuration → Virtual environment**."
+ ),
+ kind="success",
+ )
+ return (sandbox,)
+
+
+@app.cell(hide_code=True)
+def _(Path, mo):
+ screenshot_path = "sandboxes/assets/image.png"
+ mo.vstack(
+ [
+ mo.md(f"""
+ ---
+ ## 4. Start a Devin session
+
+ In Devin Cloud, start a
+ new **Agent** session, open **Virtual environment**, expand
+ **Outposts**, and select **CW Serverless Sandbox**. Then enter a
+ small prompt such as `Create a "hello world" python script for me`
+ to confirm the session is running in the remote environment.
+ """),
+ mo.image(
+ src=screenshot_path,
+ alt=(
+ "Devin session composer with Virtual environment open and "
+ "CW Serverless Sandbox selected under Outposts"
+ ),
+ width="50%",
+ rounded=True,
+ caption=(
+ "Choose the CW Serverless Sandbox Outpost before submitting "
+ "your first prompt."
+ ),
+ ),
+ ]
+ )
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 5. Clean up
+
+ The worker and sandbox consume resources while they are active. Stop the
+ sandbox when you finish the tutorial; it will also stop automatically
+ after its one-hour maximum lifetime.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ stop_button = mo.ui.run_button(label="🛑 Stop sandbox")
+ stop_button
+ return (stop_button,)
+
+
+@app.cell
+def _(mo, sandbox, stop_button):
+ mo.stop(
+ not stop_button.value,
+ mo.md("_Click **Stop sandbox** when you are finished._"),
+ )
+ sandbox.stop(missing_ok=True).result()
+ mo.md(f"Sandbox `{sandbox.sandbox_id}` stopped.")
+ return
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/sandboxes/harness-evals.py b/sandboxes/harness-evals.py
new file mode 100644
index 0000000..4b206b9
--- /dev/null
+++ b/sandboxes/harness-evals.py
@@ -0,0 +1,1485 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+# "cwsandbox==0.25.0",
+# "marimo>=0.23.8",
+# "pydantic==2.13.4",
+# "wandb[sandbox]==0.27.2",
+# "weave==0.52.38",
+# ]
+# ///
+
+import marimo
+
+__generated_with = "0.23.14"
+app = marimo.App(
+ width="medium",
+ app_title="Agent Harness Evals",
+ css_file="/usr/local/_marimo/custom.css",
+ auto_download=["html"],
+)
+
+
+@app.cell
+def _():
+ import os
+ import time
+ import json
+ import uuid
+ import weave
+ import marimo as mo
+ from pydantic import PrivateAttr
+ from wandb.sandbox import (
+ Sandbox,
+ SandboxDefaults,
+ ResourceOptions,
+ NetworkOptions,
+ SandboxTimeoutError,
+ SandboxExecutionError,
+ SandboxError,
+ )
+
+ return (
+ NetworkOptions,
+ PrivateAttr,
+ ResourceOptions,
+ Sandbox,
+ SandboxDefaults,
+ SandboxError,
+ SandboxExecutionError,
+ SandboxTimeoutError,
+ json,
+ mo,
+ os,
+ time,
+ uuid,
+ weave,
+ )
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.vstack(
+ [
+ mo.md(
+ r"""
+ # Evaluating Coding Agents Inside Serverless Sandboxes
+
+ /// admonition | About This Notebook
+ type: info
+
+ This notebook is the agent-centric full coding-agent CLI that lives inside its own sandbox**:
+
+ - The sandbox is provisioned the moment the `weave.Model` is initialized.
+ - The agent binary is installed and authenticated **headlessly**, no
+ user-led onboarding, just a W&B API key is needed.
+ - Every `predict` drives the agent *inside that sandbox* to write a solution,
+ which is then scored safely in a separate, network-isolated sandbox.
+
+ Four harnesses are wired up: **Codex**, **Claude Code**, **OpenClaw**, and
+ **Nous Hermes**, and you can score them on the built-in demo tasks or the
+ **HumanEval** / **MBPP** benchmarks (selected below).
+
+ _If you are running this notebook in edit mode, make sure you start by running all cells._
+ ///
+ """
+ ),
+ mo.md(
+ r"""
+ /// details | Prerequisites
+ type: info
+
+ - **A W&B account + API key** — from [wandb.ai/authorize](https://wandb.ai/authorize).
+ - **Provider API keys** — an OpenAI key for Codex, OpenClaw, Hermes and/or an Anthropic key for
+ Claude. Paste whichever you need into the Connect form below; an agent is only
+ runnable if its key is present.
+ ///
+ """
+ ),
+ mo.md(
+ r"""
+ /// details | Table of Contents
+ type: info
+
+ - [**Connect W&B services and provider keys**](#1-connect-wb-services-and-provider-keys) - Authenticate agents headlessly
+ - [**Define the agent backends**](#2-define-the-agent-backends) - The `AgentBackend` extensibility seam
+ - [**Score generated code safely in a separate sandbox**](#3-score-generated-code-safely-in-a-separate-sandbox) - Isolated verification
+ - [**Benchmark tasks**](#4-benchmark-tasks) - Demo, HumanEval, and MBPP
+ - [**Pick agents and launch an evaluation**](#5-pick-agents-and-launch-an-evaluation) - Run per-agent `weave.Evaluation`
+ - [**Lifecycle, discovery, and cleanup**](#6-lifecycle-discovery-and-cleanup) - Find and stop sandboxes
+ ///
+ """
+ ),
+ ]
+ )
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ # ---------- 1. Setup W&B + provider keys ----------
+ wandb_connect_form = (
+ mo.md("""
+ - W&B entity *(team or username)*: {entity}
+ - W&B project: {project}
+ - W&B API key *(required)*: {api_key}
+ - OpenAI API key *(for Codex, OpenClaw, Hermes)*: {openai_api_key}
+ - Anthropic API key *(for Claude)*: {anthropic_api_key}
+ """)
+ .batch(
+ entity=mo.ui.text(value="wandb-smle", full_width=True),
+ project=mo.ui.text(value="agent-sandbox-eval", full_width=True),
+ api_key=mo.ui.text(kind="password", placeholder="from wandb.ai/authorize", full_width=True),
+ openai_api_key=mo.ui.text(kind="password", placeholder="sk-...", full_width=True),
+ anthropic_api_key=mo.ui.text(kind="password", placeholder="sk-ant-...", full_width=True),
+ )
+ .form(submit_button_label="Connect", bordered=False)
+ )
+ wandb_connect_form
+ return (wandb_connect_form,)
+
+
+@app.cell(hide_code=True)
+def _(mo, os, wandb_connect_form, weave):
+ _v = wandb_connect_form.value or {}
+ ENTITY = _v.get("entity")
+ PROJECT = _v.get("project")
+ API_KEY = _v.get("api_key")
+ mo.stop(
+ not (ENTITY and PROJECT and API_KEY),
+ mo.md("_Fill in the W&B fields above and press **Connect**._"),
+ )
+
+ os.environ["WANDB_API_KEY"] = API_KEY
+ weave.init(f"{ENTITY}/{PROJECT}")
+ weave_url = f"https://wandb.ai/{ENTITY}/{PROJECT}/weave"
+
+ PROVIDER_KEYS = {
+ "openai_api_key": _v.get("openai_api_key") or "",
+ "anthropic_api_key": _v.get("anthropic_api_key") or "",
+ }
+ _have = [n for n, k in PROVIDER_KEYS.items() if k]
+
+ mo.callout(
+ mo.md(
+ f"✅ **Connected** — logging to `{ENTITY}/{PROJECT}`. "
+ f"[Open Weave dashboard]({weave_url}) \n"
+ f"Provider keys provided: `{', '.join(_have) or 'none yet'}`"
+ ),
+ kind="success",
+ )
+ return API_KEY, PROVIDER_KEYS
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 1. Connect W&B services and provider keys
+
+ /// admonition | Connect and authenticate agents
+ type: info
+
+ Fill in your W&B entity, project, and API key, plus the provider key(s) for
+ the agent(s) you want to run, then press **Connect**. Keys are kept in memory
+ and injected into each agent's sandbox as environment variables
+ (`OPENAI_API_KEY` for Codex, `ANTHROPIC_API_KEY` for
+ Claude). Nothing is written to disk and no interactive login is triggered.
+
+ The form gates the rest of the notebook: the agent definitions, scorer, and
+ evaluation cells stay paused until you connect.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ wandb_product_tabs = mo.ui.tabs(
+ {
+ "Agents": mo.md(
+ """
+ ### Coding agents as sandboxed models
+
+ Each agent is a real terminal coding tool driven headlessly
+ (`codex exec`, `claude -p`). Inside its sandbox the agent reads the
+ task, writes Python, and saves a final `solution.py`, exactly the
+ workflow a developer would run locally, but isolated and reproducible.
+ """
+ ),
+ "Weave": mo.md(
+ """
+ ### W&B Weave
+
+ Weave traces the full lifecycle: the agent's generation
+ (`predict`), the returned solution, the scorer's pass/fail verdict,
+ and latency — making it easy to compare agents side by side.
+ """
+ ),
+ "Sandbox": mo.md(
+ """
+ ### Serverless Sandbox
+
+ Two sandbox roles here: the **agent sandbox** (one per model,
+ provisioned at init, internet egress enabled so the agent can call
+ its model API and `npm install`) and the **scorer sandbox** (a
+ fresh, network-isolated sandbox per scored solution, so untrusted
+ generated code can't phone home).
+ """
+ ),
+ }
+ )
+
+ wandb_product_tabs
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 2. Define the agent backends
+
+ /// admonition | AgentBackend
+ type: info
+
+ `AgentBackend` is the extensibility seam: each concrete backend knows how to
+ **install** itself, what **auth env** it needs, and how to build the headless
+ **run command**.
+ ///
+ """)
+ return
+
+
+@app.cell
+def _():
+ # ---------- 2.1 Agent backend abstraction ----------
+ import shlex
+ from abc import ABC, abstractmethod
+
+ class AgentBackend(ABC):
+ id: str
+ display_name: str
+ provider: str
+ key_field: str
+ eval_parallelism: int | None = None
+ run_timeout_seconds: int | None = None
+
+ @abstractmethod
+ def install_cmds(self) -> list:
+ """Commands run once inside the sandbox at provision time."""
+
+ @abstractmethod
+ def auth_env(self, api_key: str) -> dict:
+ """Env vars (baked into the agent sandbox) for non-interactive auth."""
+
+ @abstractmethod
+ def run_argv(self, prompt: str, workdir: str, model: str | None) -> list:
+ """argv to run the agent headlessly for a single task."""
+
+ class CodexBackend(AgentBackend):
+ id = "codex"
+ display_name = "Codex CLI"
+ provider = "OpenAI"
+ key_field = "openai_api_key"
+
+ def install_cmds(self) -> list:
+ return [["npm", "install", "-g", "@openai/codex"]]
+
+ def auth_env(self, api_key: str) -> dict:
+ return {"CODEX_API_KEY": api_key, "OPENAI_API_KEY": api_key}
+
+ def run_argv(self, prompt: str, workdir: str, model: str | None) -> list:
+ # We're already inside an isolated W&B sandbox, so disable Codex's OWN
+ # nested OS sandbox (landlock/seccomp).
+ argv = [
+ "codex", "exec",
+ "--skip-git-repo-check",
+ "--sandbox", "danger-full-access",
+ "--cd", workdir,
+ ]
+ if model:
+ argv += ["-m", model]
+ argv.append(prompt)
+ return argv
+
+ class ClaudeCodeBackend(AgentBackend):
+ id = "claude"
+ display_name = "Claude Code CLI"
+ provider = "Anthropic"
+ key_field = "anthropic_api_key"
+
+ def install_cmds(self) -> list:
+ return [["npm", "install", "-g", "@anthropic-ai/claude-code"]]
+
+ def auth_env(self, api_key: str) -> dict:
+ # IS_SANDBOX=1 is Claude Code's documented escape hatch: W&B sandbox
+ # runs as root
+ return {"ANTHROPIC_API_KEY": api_key, "IS_SANDBOX": "1"}
+
+ def run_argv(self, prompt: str, workdir: str, model: str | None) -> list:
+ inner = ["claude", "-p", prompt, "--bare", "--dangerously-skip-permissions"]
+ if model:
+ inner += ["--model", model]
+ return ["bash", "-lc", f"cd {shlex.quote(workdir)} && {shlex.join(inner)}"]
+
+ class OpenClawBackend(AgentBackend):
+ # OpenClaw is a full agent HARNESS (Gateway + workspace + tools)
+ id = "openclaw"
+ display_name = "OpenClaw"
+ eval_parallelism = 1
+ run_timeout_seconds = 300
+ provider = "OpenAI"
+ key_field = "openai_api_key"
+
+ workspace = "/work/openclaw-ws"
+
+ def install_cmds(self) -> list:
+ onboard = (
+ f"mkdir -p {shlex.quote(self.workspace)} && "
+ "openclaw onboard --non-interactive --mode local "
+ f"--workspace {shlex.quote(self.workspace)} "
+ "--auth-choice openai-api-key --secret-input-mode ref "
+ "--accept-risk --skip-skills --skip-bootstrap --skip-health"
+ )
+ return [
+ ["npm", "install", "-g", "openclaw@latest"],
+ ["bash", "-lc", onboard],
+ ]
+
+ def auth_env(self, api_key: str) -> dict:
+ return {"OPENAI_API_KEY": api_key}
+
+ def run_argv(self, prompt: str, workdir: str, model: str | None) -> list:
+ return [
+ "bash", "-lc",
+ f"openclaw agent --local --agent main --message {shlex.quote(prompt)}",
+ ]
+
+ class HermesBackend(AgentBackend):
+ # Hermes (Nous Research) is a self-improving agent harness
+ id = "hermes"
+ display_name = "Hermes (Nous)"
+ provider = "OpenAI"
+ key_field = "openai_api_key"
+ hermes_provider = "openai-api"
+ default_model = "gpt-5.5"
+
+ def install_cmds(self) -> list:
+ return [[
+ "bash", "-lc",
+ "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash",
+ ]]
+
+ def auth_env(self, api_key: str) -> dict:
+ return {
+ "OPENAI_API_KEY": api_key,
+ "HERMES_YOLO_MODE": "1", # bypass dangerous-command approval
+ "HERMES_ACCEPT_HOOKS": "1", # auto-approve shell hooks (no TTY)
+ }
+
+ def run_argv(self, prompt: str, workdir: str, model: str | None) -> list:
+ m = model or self.default_model
+ cmd = (
+ f"hermes -z {shlex.quote(prompt)} "
+ f"--provider {self.hermes_provider} --model {shlex.quote(m)}"
+ )
+ return [
+ "bash", "-lc",
+ 'export PATH="$HOME/.local/bin:$PATH" && '
+ f"cd {shlex.quote(workdir)} && {cmd}",
+ ]
+
+ BACKENDS = {
+ b.id: b
+ for b in (CodexBackend(), ClaudeCodeBackend(), OpenClawBackend(), HermesBackend())
+ }
+ return (BACKENDS,)
+
+
+@app.cell
+def _(ResourceOptions):
+ # Agent sandboxes need Node (for the CLIs) and git; node:22-bookworm has both.
+ # They get more CPU/memory than the scorer because they run a full agent loop.
+ # ONE sandbox per model serves all concurrent predicts (see DEFAULT_PARALLELISM),
+ # and each `claude`/`codex`/`openclaw`/`hermes` run is a heavy Node/Rust process, so the
+ # LIMITS are generous to avoid OOM-killing concurrent agents (which manifests as
+ # empty-output timeouts). Requests stay small so provisioning isn't rejected.
+ AGENT_IMAGE = "node:22-bookworm"
+ AGENT_RESOURCES = ResourceOptions(
+ requests={"cpu": "500m", "memory": "512Mi"},
+ limits={"cpu": "4", "memory": "6Gi"},
+ )
+ return AGENT_IMAGE, AGENT_RESOURCES
+
+
+@app.cell
+def _(
+ AGENT_IMAGE,
+ AGENT_RESOURCES,
+ BACKENDS,
+ NetworkOptions,
+ PrivateAttr,
+ Sandbox,
+ SandboxDefaults,
+ SandboxTimeoutError,
+ uuid,
+ weave,
+):
+ # ---------- 2.2 Sandboxed-agent weave.Model ----------
+ import hashlib
+ import threading
+
+ _AGENT_SANDBOXES: dict = {}
+ _PROVISION_LOCK = threading.Lock()
+
+ AGENT_MAX_LIFETIME_SECONDS = 3600
+
+ class SandboxAgentModel(weave.Model):
+ agent_id: str
+ model_name: str | None = None
+ # Two independent budgets: a one-time CLI install vs. each agent task run.
+ # Codex/Claude return in seconds on these tasks, so 180s is a generous run
+ # ceiling that fails fast if one hangs.
+ install_timeout_seconds: int = 900
+ run_timeout_seconds: int = 180
+ # Sandbox self-termination backstop. Defaults to the floor; the run loop
+ # raises it for serial agents so the sandbox outlives the full eval.
+ max_lifetime_seconds: int = AGENT_MAX_LIFETIME_SECONDS
+ # Serialized link: the sandbox this agent is bound to. Set during setup();
+ # lets us re-attach (Sandbox.from_id) and stop() the exact sandbox at any
+ # time. (It changes per run, so each run logs a new model version.)
+ sandbox_id: str | None = None
+
+ # Private (not serialized): the provider key, kept out of the trace/version.
+ _api_key: str = PrivateAttr(default="")
+
+ # ---- lifecycle ----
+ def _cache_key(self) -> tuple:
+ digest = (
+ hashlib.sha256(self._api_key.encode()).hexdigest()[:8]
+ if self._api_key
+ else "nokey"
+ )
+ return (self.agent_id, self.model_name, digest)
+
+ def _provision(self):
+ """Spin up the agent's sandbox and install + authenticate the CLI once."""
+ backend = BACKENDS[self.agent_id]
+ defaults = SandboxDefaults(
+ container_image=AGENT_IMAGE,
+ tags=("agent-sandbox-eval", self.agent_id),
+ environment_variables={
+ **backend.auth_env(self._api_key),
+ "NODE_NO_WARNINGS": "1",
+ "CI": "1",
+ },
+ resources=AGENT_RESOURCES,
+ )
+ # Internet egress: required for npm install AND the agent's model API.
+ sb = Sandbox.run(
+ defaults=defaults,
+ network=NetworkOptions(egress_mode="internet"),
+ max_lifetime_seconds=self.max_lifetime_seconds,
+ )
+ for cmd in backend.install_cmds():
+ proc = sb.exec(cmd, timeout_seconds=self.install_timeout_seconds).result()
+ if proc.returncode != 0:
+ try:
+ sb.stop(missing_ok=True).result()
+ except Exception:
+ pass
+ raise RuntimeError(
+ f"`{' '.join(cmd)}` failed (rc={proc.returncode}): "
+ f"{(proc.stderr or '')[-400:]}"
+ )
+ return sb
+
+ @weave.op(name="agent_setup")
+ def setup(self) -> dict:
+ """Lazily spin up + install + authenticate the agent's sandbox once, then
+ cache the handle and bind self.sandbox_id to it. Wrapped as a weave.op so
+ provisioning (and any failure) is traced as OUTPUT rather than raising and
+ crashing the run. Idempotent: reuses a cached/known sandbox if present."""
+ key = self._cache_key()
+ sb = _AGENT_SANDBOXES.get(key)
+ if sb is None:
+ with _PROVISION_LOCK:
+ sb = _AGENT_SANDBOXES.get(key) # re-check after acquiring the lock
+ if sb is None:
+ try:
+ if self.sandbox_id is not None:
+ # Known id, no cached handle (fresh process) -> reattach.
+ sb = Sandbox.from_id(self.sandbox_id).result()
+ else:
+ sb = self._provision()
+ except Exception as e:
+ return {
+ "agent_id": self.agent_id,
+ "model_name": self.model_name,
+ "status": "error",
+ "error": f"{type(e).__name__}: {e}",
+ }
+ _AGENT_SANDBOXES[key] = sb
+ self.sandbox_id = sb.sandbox_id
+ return {
+ "agent_id": self.agent_id,
+ "model_name": self.model_name,
+ "sandbox_id": self.sandbox_id,
+ "status": "ready",
+ }
+
+ def _get_sandbox(self):
+ """Return (live_handle_or_None, setup_status). Provisions via setup() on
+ first use; predict relies on this so a setup failure becomes a failed row
+ instead of an exception."""
+ sb = _AGENT_SANDBOXES.get(self._cache_key())
+ if sb is not None:
+ self.sandbox_id = sb.sandbox_id
+ return sb, {"status": "ready", "sandbox_id": self.sandbox_id}
+ status = self.setup()
+ return _AGENT_SANDBOXES.get(self._cache_key()), status
+
+ def ensure_ready(self) -> dict:
+ """Public hook for the run loop to provision before evaluate (clean error
+ reporting + keeps the first row's latency from absorbing the install).
+ Returns the setup() status dict."""
+ return self.setup()
+
+ @weave.op(name="agent_generate_solution", kind="llm")
+ def predict(self, name: str, spec: str, tests: list) -> str:
+ import shlex as _shlex
+
+ backend = BACKENDS[self.agent_id]
+ sb, status = self._get_sandbox()
+ if sb is None:
+ return f"failed: setup error: {status.get('error', 'unknown')}"
+
+ run_id = uuid.uuid4().hex
+ workdir = f"/work/{run_id}"
+ target = f"{workdir}/solution.py"
+ # Logs live OUTSIDE workdir so the agent doesn't see/touch them.
+ out_log, err_log = f"/tmp/{run_id}.out", f"/tmp/{run_id}.err"
+ try:
+ sb.exec(["mkdir", "-p", workdir]).result()
+ except Exception as e: # noqa: BLE001
+ # The sandbox can be GONE here — e.g. it hit max_lifetime_seconds
+ # mid-eval (serial agents) and the gRPC call returns NOT_FOUND.
+ # Record a failed row instead of letting it crash the eval thread.
+ return (
+ f"failed: agent sandbox unavailable ({type(e).__name__}); it may "
+ f"have exceeded its lifetime. {str(e)[-200:]}"
+ )
+
+ prompt = (
+ f"You are solving a coding task. Write a Python function "
+ f"named `{name}` that satisfies the specification below.\n\n"
+ f"Specification:\n{spec}\n\n"
+ f"Requirements:\n"
+ f"- Use the exact function name and signature requested.\n"
+ f"- Save the complete solution — the function definition plus any "
+ f"imports it needs (no markdown fences, no prose) — to the file: "
+ f"{target}\n"
+ f"- Overwrite the file if it already exists.\n"
+ )
+ argv = backend.run_argv(prompt, workdir, self.model_name)
+ shell = (
+ f"{_shlex.join(argv)} < /dev/null "
+ f"> {_shlex.quote(out_log)} 2> {_shlex.quote(err_log)}"
+ )
+
+ def _tail(path: str, n: int = 500) -> str:
+ try:
+ return sb.read_file(path).result().decode()[-n:].strip()
+ except Exception:
+ return ""
+
+ try:
+ sb.exec(
+ ["bash", "-lc", shell], timeout_seconds=self.run_timeout_seconds
+ ).result()
+ except SandboxTimeoutError:
+ # Catch the PARENT timeout (covers SandboxCommandTimeoutError too) so a
+ # slow/hung agent records a failed row (with diagnostics) instead of
+ # crashing the eval. Capture BOTH streams
+ return (
+ f"failed: agent run timed out after {self.run_timeout_seconds}s. "
+ f"stdout tail: {_tail(out_log)} | stderr tail: {_tail(err_log)}"
+ )
+ except Exception as e: # noqa: BLE001
+ # Sandbox vanished during the run (e.g. lifetime exceeded -> NOT_FOUND).
+ return (
+ f"failed: agent sandbox error during run ({type(e).__name__}); it "
+ f"may have exceeded its lifetime. {str(e)[-200:]}"
+ )
+
+ # Prefer the file the agent wrote; surface both streams otherwise.
+ try:
+ code = sb.read_file(target).result().decode()
+ if code.strip():
+ return code.strip()
+ except Exception:
+ pass
+ # Show what (if anything) the agent left in the workdir — distinguishes
+ # "wrote nothing" from "wrote a differently-named file" at a glance.
+ try:
+ _ls = (
+ sb.exec(["bash", "-lc", f"ls -la {_shlex.quote(workdir)}"])
+ .result()
+ .stdout
+ or ""
+ ).strip()[-200:]
+ except Exception:
+ _ls = ""
+ return (
+ f"failed: no solution.py written. workdir: {_ls} | "
+ f"stdout tail: {_tail(out_log)} | stderr tail: {_tail(err_log)}"
+ )
+
+ def stop(self) -> None:
+ """Spin down the sandbox bound to this agent — via the cached handle or,
+ if that's gone, by re-attaching with the serialized sandbox_id."""
+ sb = _AGENT_SANDBOXES.pop(self._cache_key(), None)
+ if sb is None and self.sandbox_id:
+ try:
+ sb = Sandbox.from_id(self.sandbox_id).result()
+ except Exception:
+ sb = None
+ if sb is not None:
+ try:
+ sb.stop(missing_ok=True).result()
+ except Exception:
+ pass
+
+
+ return (SandboxAgentModel,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 3. Score generated code safely in a separate sandbox
+
+ /// admonition | CodeScorer
+ type: info
+
+ The agent's output is verified by the **`CodeScorer`**: it wraps each task's `(input, expected)` pairs in a
+ harness, runs the solution in a **fresh, network-isolated** sandbox, and
+ returns `{passed, error, sandbox_latency}`. Keeping scoring in its own
+ egress-free sandbox means untrusted generated code can't reach the network,
+ even though the agent sandbox can.
+ ///
+ """)
+ return
+
+
+@app.cell
+def _(ResourceOptions, SandboxDefaults):
+ # Scorer sandboxes: small, no special networking (isolated by default).
+ SANDBOX_DEFAULTS = SandboxDefaults(
+ container_image="python:3.11",
+ tags=("agent-sandbox-eval", "code-eval"),
+ environment_variables={"PYTHONUNBUFFERED": "1"},
+ resources=ResourceOptions(
+ requests={"cpu": "250m", "memory": "256Mi"},
+ limits={"cpu": "1", "memory": "512Mi"},
+ ),
+ )
+ return (SANDBOX_DEFAULTS,)
+
+
+@app.cell
+def _(
+ SANDBOX_DEFAULTS,
+ Sandbox,
+ SandboxError,
+ SandboxExecutionError,
+ SandboxTimeoutError,
+ json,
+ time,
+ weave,
+):
+ # ---------- 3.1 Sandbox-based scorer ----------
+ class CodeScorer(weave.Scorer):
+
+ @weave.op(name="code_scorer", kind="scorer")
+ def score(
+ self,
+ name: str,
+ spec: str,
+ tests: list,
+ output: str,
+ test_program: str | None = None,
+ entry_point: str | None = None,
+ ) -> dict:
+ start_time = time.time()
+ _out = (output or "").strip()
+ if not _out or _out.startswith("failed:"):
+ return {
+ "passed": False,
+ "error": (_out or "empty generation")[-300:],
+ "sandbox_latency": time.time() - start_time,
+ }
+
+
+ if test_program:
+ _ep = entry_point or name
+ script = (
+ f"{output}\n\n"
+ f"{test_program}\n\n"
+ "import json\n"
+ "_passed, _error = True, ''\n"
+ "try:\n"
+ f" check({_ep})\n"
+ "except Exception as _e:\n"
+ " _passed, _error = False, repr(_e)\n"
+ "with open('/tmp/result.json', 'w') as _f:\n"
+ " json.dump({'passed': _passed, 'error': _error}, _f)\n"
+ )
+ else:
+ asserts = "\n".join(
+ f" assert {name}(*{args!r}) == {expected!r}, {f'failed on input {args!r}'!r}"
+ for args, expected in tests
+ )
+ script = (
+ f"{output}\n\n"
+ "import json\n"
+ "_passed, _error = True, ''\n"
+ "try:\n"
+ f"{asserts}\n"
+ "except Exception as _e:\n"
+ " _passed, _error = False, repr(_e)\n"
+ "with open('/tmp/result.json', 'w') as _f:\n"
+ " json.dump({'passed': _passed, 'error': _error}, _f)\n"
+ )
+
+ last_exc = None
+ for attempt in range(3):
+ sb = None
+ try:
+ sb = Sandbox.run(defaults=SANDBOX_DEFAULTS)
+ sb.write_file("/tmp/t.py", script.encode()).result()
+ proc = sb.exec(
+ ["python", "/tmp/t.py"], timeout_seconds=10
+ ).result()
+ try:
+ verdict = json.loads(
+ sb.read_file("/tmp/result.json").result().decode()
+ )
+ passed = bool(verdict.get("passed"))
+ error = str(verdict.get("error", ""))
+ except Exception:
+ passed = False
+ error = (proc.stderr or "no result file written")
+ end_time = time.time()
+ return {
+ "passed": passed,
+ "error": error[-300:],
+ "sandbox_latency": (end_time - start_time),
+ }
+ except SandboxTimeoutError:
+ # Parent timeout (caught before SandboxError below)
+ end_time = time.time()
+ return {
+ "passed": False,
+ "error": "execution timed out after 10s",
+ "sandbox_latency": (end_time - start_time),
+ }
+ except (SandboxExecutionError, SandboxError) as e:
+ last_exc = e
+ time.sleep(2 ** attempt)
+ finally:
+ if sb is not None:
+ try:
+ sb.stop(missing_ok=True).result()
+ except Exception:
+ pass
+ end_time = time.time()
+ return {"passed": False, "error": f"Sandbox unavailable after 3 attempts: {last_exc}", "sandbox_latency": (end_time - start_time)}
+
+ return (CodeScorer,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 4. Benchmark tasks
+
+ /// admonition | Build a versioned Weave Dataset
+ type: info
+
+ Pick the problem set the agents solve. Two are wired up behind a small
+ `BENCHMARKS` registry (the data sibling of the `AgentBackend` seam):
+
+ - **Built-in demo** — the LeetCode-style easy/medium/hard prompts with
+ deterministic `(input, expected)` tuple tests.
+ - **HumanEval** — the canonical 164-problem coding benchmark, downloaded on
+ demand. Each problem is a function signature + docstring; a hidden
+ `check()` program verifies the agent's solution.
+ - **MBPP** — the 974-problem "Mostly Basic Python Problems" set. Each problem
+ is a short natural-language task; the agent writes the named function and a
+ hidden batch of asserts (wrapped as a `check()`) verifies it.
+
+ All are normalized to the same row schema, so the same `predict` and
+ `CodeScorer` handle either. The difference from the code-gen tutorial is
+ *who* solves them: here a coding agent in a sandbox, not a single API call.
+ Choose a benchmark and the tasks to include; the selection builds a
+ versioned Weave `Dataset`.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _():
+ # ---------- 4.1 Ground-truth dataset ----------
+ TASKS = [
+ # --- Easy ---
+ {
+ "name": "add",
+ "spec": "Write a function `add(a, b)` that returns the sum of two numbers.",
+ "tests": [((2, 3), 5), ((-1, 1), 0), ((0, 0), 0)],
+ },
+ {
+ "name": "reverse_string",
+ "spec": "Write a function `reverse_string(s)` that returns the reverse of string s.",
+ "tests": [(("hello",), "olleh"), (("",), ""), (("a",), "a")],
+ },
+ {
+ "name": "fizzbuzz",
+ "spec": "Write a function `fizzbuzz(n)` returning 'Fizz' if n is divisible by 3, 'Buzz' if by 5, 'FizzBuzz' if by both, else str(n).",
+ "tests": [((3,), "Fizz"), ((5,), "Buzz"), ((15,), "FizzBuzz"), ((7,), "7")],
+ },
+ # --- Medium ---
+ {
+ "name": "is_prime",
+ "spec": "Write a function `is_prime(n)` that returns True iff n is a prime number. n is a positive integer.",
+ "tests": [((2,), True), ((4,), False), ((17,), True), ((1,), False)],
+ },
+ {
+ "name": "second_largest",
+ "spec": "Write a function `second_largest(nums)` returning the second-largest distinct value in a list of ints. Assume len(nums) >= 2 and at least 2 distinct values.",
+ "tests": [(([1, 2, 3],), 2), (([5, 5, 4, 4, 3],), 4), (([-1, -2, -3],), -2)],
+ },
+ {
+ "name": "flatten",
+ "spec": "Write a function `flatten(lst)` that takes a possibly nested list of ints and returns a flat list of all ints in order.",
+ "tests": [
+ (([1, [2, [3, 4]], 5],), [1, 2, 3, 4, 5]),
+ (([],), []),
+ (([[1], [2, [3]]],), [1, 2, 3]),
+ ],
+ },
+ {
+ "name": "group_anagrams",
+ "spec": "Write a function `group_anagrams(words)` that groups a list of strings into lists of anagrams. Each group must be sorted alphabetically internally. The returned list of groups must be sorted by the first element of each group.",
+ "tests": [
+ ((["eat", "tea", "tan", "ate", "nat", "bat"],), [["ate", "eat", "tea"], ["bat"], ["nat", "tan"]]),
+ (([""],), [[""]]),
+ ((["a"],), [["a"]]),
+ ],
+ },
+ {
+ "name": "longest_common_subsequence",
+ "spec": "Write a function `longest_common_subsequence(s1, s2)` that returns the length of the longest common subsequence of strings s1 and s2.",
+ "tests": [
+ (("abcde", "ace"), 3),
+ (("abc", "abc"), 3),
+ (("abc", "def"), 0),
+ ],
+ },
+ # --- Hard ---
+ {
+ "name": "min_coins",
+ "spec": "Write a function `min_coins(coins, amount)` that returns the minimum number of coins needed to make up the given amount using the given coin denominations. Return -1 if it is not possible.",
+ "tests": [
+ (([1, 5, 11], 15), 3),
+ (([2], 3), -1),
+ (([1, 2, 5], 11), 3),
+ ],
+ },
+ {
+ "name": "longest_palindrome",
+ "spec": "Write a function `longest_palindrome(s)` that returns the longest palindromic substring of s. If there are ties, return the one that starts earliest.",
+ "tests": [
+ (("babad",), "bab"),
+ (("cbbd",), "bb"),
+ (("a",), "a"),
+ (("racecar",), "racecar"),
+ ],
+ },
+ {
+ "name": "word_break",
+ "spec": "Write a function `word_break(s, word_dict)` that returns True if the string s can be segmented into a space-separated sequence of one or more words from word_dict (a list of strings).",
+ "tests": [
+ (("leetcode", ["leet", "code"]), True),
+ (("applepenapple", ["apple", "pen"]), True),
+ (("catsandog", ["cats", "dog", "sand", "and", "cat"]), False),
+ ],
+ },
+ ]
+ return (TASKS,)
+
+
+@app.cell
+def _(TASKS):
+ # ---------- 4.2 Benchmark loaders ----------
+ # Every benchmark, demo or real, is normalized to ONE row schema so the
+ # weave.Dataset, predict(), and CodeScorer never have to branch on the source:
+ # name -> the function the agent must write
+ # spec -> the problem text shown to the agent (tests stay hidden)
+ # tests -> [(args, expected), ...] tuple asserts (demo benchmark)
+ # test_program -> a `def check(candidate): assert ...` program (HumanEval, MBPP)
+ # entry_point -> the function `check` is called with
+ # source -> a human label/id for the row (difficulty or task_id)
+ # A row uses EITHER `tests` (tuple asserts) OR `test_program` (a check fn);
+ # the scorer prefers `test_program` when present.
+ import gzip
+ import json as _json
+ import re as _re
+ import urllib.request
+
+ # Official HumanEval problem set (164 problems). /raw/ redirects to
+ # raw.githubusercontent.com, which urllib follows automatically.
+ HUMANEVAL_URL = (
+ "https://github.com/openai/human-eval/raw/master/data/HumanEval.jsonl.gz"
+ )
+ # MBPP "Mostly Basic Python Problems" (974 problems), one JSON object per line.
+ MBPP_URL = (
+ "https://github.com/google-research/google-research/raw/master/mbpp/mbpp.jsonl"
+ )
+ _BENCH_CACHE: dict = {}
+
+ def _difficulty_for(index: int) -> str:
+ return "Easy" if index < 3 else "Medium" if index < 8 else "Hard"
+
+ def load_demo(limit: int | None = None) -> list:
+ """The 11 hand-written LeetCode-style tasks, normalized to the schema."""
+ rows = [
+ {
+ "name": _t["name"],
+ "spec": _t["spec"],
+ "tests": _t["tests"],
+ "test_program": None,
+ "entry_point": _t["name"],
+ "source": _difficulty_for(_i),
+ }
+ for _i, _t in enumerate(TASKS)
+ ]
+ return rows[:limit] if limit else rows
+
+ def _ssl_context():
+ # Standalone/uv-managed interpreters don't always trust the OS keychain,
+ # which makes a plain urllib HTTPS GET fail with CERTIFICATE_VERIFY_FAILED.
+ # certifi ships transitively with wandb/weave, so use its CA bundle when
+ # available and fall back to the default context otherwise.
+ import ssl
+ try:
+ import certifi
+ return ssl.create_default_context(cafile=certifi.where())
+ except Exception: # noqa: BLE001
+ return ssl.create_default_context()
+
+ def load_humaneval(limit: int | None = None) -> list:
+ """HumanEval: download + cache the JSONL, map each problem to the schema.
+ Runs in the notebook process (which already needs internet), not a sandbox."""
+ if "humaneval" not in _BENCH_CACHE:
+ _req = urllib.request.Request(
+ HUMANEVAL_URL, headers={"User-Agent": "Mozilla/5.0"}
+ )
+ _raw = urllib.request.urlopen(_req, timeout=60, context=_ssl_context()).read()
+ _text = gzip.decompress(_raw).decode()
+ _problems = [
+ _json.loads(_line) for _line in _text.splitlines() if _line.strip()
+ ]
+ _BENCH_CACHE["humaneval"] = [
+ {
+ "name": _p["entry_point"],
+ "spec": _p["prompt"],
+ "tests": [],
+ "test_program": _p["test"],
+ "entry_point": _p["entry_point"],
+ "source": _p["task_id"],
+ }
+ for _p in _problems
+ ]
+ rows = _BENCH_CACHE["humaneval"]
+ return rows[:limit] if limit else rows
+
+ def _mbpp_entry_point(code: str, test_list: list) -> str:
+ # MBPP `code` may define helper functions/globals before the real one, so
+ # pick the def that's actually CALLED in the tests; fall back to the last def.
+ defs = _re.findall(r"(?m)^[ \t]*def\s+(\w+)\s*\(", code)
+ called = " ".join(test_list)
+ for _d in defs:
+ if _re.search(rf"\b{_re.escape(_d)}\s*\(", called):
+ return _d
+ return defs[-1] if defs else "solution"
+
+ def _mbpp_test_program(test_list: list, setup_code: str) -> str:
+ # Wrap MBPP's bare assert strings (which reference the function by its real
+ # name) in a `check(candidate)` so the SAME scorer path as HumanEval runs
+ # them. `candidate` is unused — the asserts hit the global the agent defined.
+ body = []
+ for _line in (setup_code or "").splitlines():
+ body.append((" " + _line) if _line.strip() else "")
+ for _assert in test_list:
+ body.extend(" " + _line for _line in _assert.splitlines())
+ if not any(_l.strip() for _l in body):
+ body = [" pass"]
+ return "def check(candidate):\n" + "\n".join(body) + "\n"
+
+ def load_mbpp(limit: int | None = None) -> list:
+ """MBPP: download + cache the JSONL, map each problem to the schema. The
+ function name is derived from the reference code; the agent gets only the
+ natural-language `text` (tests stay hidden), same as the other benchmarks."""
+ if "mbpp" not in _BENCH_CACHE:
+ _req = urllib.request.Request(
+ MBPP_URL, headers={"User-Agent": "Mozilla/5.0"}
+ )
+ _text = (
+ urllib.request.urlopen(_req, timeout=60, context=_ssl_context())
+ .read()
+ .decode()
+ )
+ _problems = [
+ _json.loads(_line) for _line in _text.splitlines() if _line.strip()
+ ]
+ _rows = []
+ for _p in _problems:
+ _ep = _mbpp_entry_point(_p["code"], _p["test_list"])
+ _rows.append(
+ {
+ "name": _ep,
+ "spec": _p["text"],
+ "tests": [],
+ "test_program": _mbpp_test_program(
+ _p["test_list"], _p.get("test_setup_code", "")
+ ),
+ "entry_point": _ep,
+ "source": f"MBPP/{_p['task_id']}",
+ }
+ )
+ _BENCH_CACHE["mbpp"] = _rows
+ rows = _BENCH_CACHE["mbpp"]
+ return rows[:limit] if limit else rows
+
+ # Registry mirrors BACKENDS: adding BigCodeBench later is one more entry.
+ BENCHMARKS = {
+ "demo": {"display": "Built-in demo (11 tasks)", "load": load_demo, "size": 11},
+ "humaneval": {"display": "HumanEval (164 problems)", "load": load_humaneval, "size": 164},
+ # "mbpp": {"display": "MBPP (974 problems)", "load": load_mbpp, "size": 974},
+ }
+ return (BENCHMARKS,)
+
+
+@app.cell(hide_code=True)
+def _(BENCHMARKS, mo):
+ # ---------- 4.3 Benchmark picker ----------
+ benchmark_dropdown = mo.ui.dropdown(
+ options={_v["display"]: _k for _k, _v in BENCHMARKS.items()},
+ value=BENCHMARKS["humaneval"]["display"],
+ label="Benchmark",
+ )
+ # A benchmark x 4 agents x (agent run + scorer sandbox) is expensive, so cap the
+ # count by default; raise it for a fuller run. Stop at the largest benchmark.
+ _max_size = max(_v["size"] for _v in BENCHMARKS.values())
+ max_problems = mo.ui.number(start=1, stop=_max_size, step=1, value=20, label="Max problems")
+ mo.vstack(
+ [
+ mo.md(
+ """
+ ### Choose a benchmark
+
+ Pick the problem set the agents will solve. **Built-in demo** is the
+ 11 hand-written tasks; **HumanEval** (164) and **MBPP** (974) are the
+ canonical function-completion benchmarks, downloaded on demand. Use
+ **Max problems** to cap how many are loaded — each problem is run by
+ every selected agent and scored in its own sandbox, so a full
+ benchmark x 4-agent sweep is costly.
+ """
+ ),
+ mo.hstack([benchmark_dropdown, max_problems], justify="start", gap=2),
+ ]
+ )
+ return benchmark_dropdown, max_problems
+
+
+@app.cell(hide_code=True)
+def _(BENCHMARKS, benchmark_dropdown, max_problems, mo):
+ # ---------- 4.4 Load the selected benchmark ----------
+ benchmark_id = benchmark_dropdown.value or "demo"
+ _limit = int(max_problems.value) if max_problems.value else None
+
+ try:
+ loaded_tasks = BENCHMARKS[benchmark_id]["load"](_limit)
+ _load_error = None
+ except Exception as _e: # noqa: BLE001 — surface as a callout, never crash the notebook
+ loaded_tasks = []
+ _load_error = f"{type(_e).__name__}: {_e}"
+
+ if _load_error:
+ _bench_summary = mo.callout(
+ mo.md(
+ f"⚠️ Could not load **{BENCHMARKS[benchmark_id]['display']}**: "
+ f"`{_load_error}`. Check your machine's internet access and retry."
+ ),
+ kind="danger",
+ )
+ else:
+ _bench_summary = mo.callout(
+ mo.md(
+ f"**Benchmark:** `{BENCHMARKS[benchmark_id]['display']}` \n"
+ f"**Loaded problems:** `{len(loaded_tasks)}` "
+ f"(of `{BENCHMARKS[benchmark_id]['size']}` available)"
+ ),
+ kind="success",
+ )
+ _bench_summary
+ return benchmark_id, loaded_tasks
+
+
+@app.cell(hide_code=True)
+def _(loaded_tasks, mo):
+ _all_task_rows = [
+ {
+ "#": _i,
+ "source": _t["source"],
+ "function": _t["name"],
+ "checks": "check()" if _t.get("test_program") else f"{len(_t['tests'])} asserts",
+ "spec": (_t["spec"][:120] + "…") if len(_t["spec"]) > 120 else _t["spec"],
+ }
+ for _i, _t in enumerate(loaded_tasks)
+ ]
+ task_table = mo.ui.table(
+ _all_task_rows,
+ selection="multi",
+ initial_selection=list(range(len(_all_task_rows))),
+ label="Select the benchmark tasks to include in this evaluation",
+ page_size=20,
+ )
+ task_table
+ return (task_table,)
+
+
+@app.cell(hide_code=True)
+def _(benchmark_id, loaded_tasks, mo, task_table, weave):
+ # Map the selected table rows back to their full schema dicts by index.
+ _selected_idx = sorted(
+ _row["#"] for _row in (task_table.value or []) if "#" in _row
+ )
+ selected_benchmark_tasks = [loaded_tasks[_i] for _i in _selected_idx]
+
+ dataset_name = f"{benchmark_id}_{len(selected_benchmark_tasks)}tasks"
+
+ dataset = (
+ weave.Dataset(name=dataset_name, rows=selected_benchmark_tasks)
+ if selected_benchmark_tasks
+ else None
+ )
+
+ if selected_benchmark_tasks:
+ _summary = mo.callout(
+ mo.md(
+ f"**Weave dataset:** `{dataset_name}` \n"
+ f"**Selected tasks:** `{len(selected_benchmark_tasks)}` of "
+ f"`{len(loaded_tasks)}` loaded"
+ ),
+ kind="success",
+ )
+ else:
+ _summary = mo.callout(
+ mo.md("No tasks selected — check one or more rows in the table above to build a dataset."),
+ kind="warn",
+ )
+ _summary
+ return dataset, dataset_name, selected_benchmark_tasks
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 5. Pick agents and launch an evaluation
+
+ /// admonition | Run a per-agent weave.Evaluation
+ type: info
+
+ Choose the agent(s) to evaluate, then press **Run selected evaluation**. Each
+ selected agent is wrapped in a `SandboxAgentModel`, which provisions its own
+ sandbox (install + auth) before the run, solves every task inside that
+ sandbox, and is scored by `CodeScorer`. Results are logged as a
+ `weave.Evaluation` per agent. Agent sandboxes are torn down when the run
+ completes.
+
+ An agent is only runnable if its provider key was entered in the Connect
+ form, rows without a key are flagged below.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(BACKENDS, PROVIDER_KEYS, mo):
+ _agent_rows = [
+ {
+ "agent_id": _b.id,
+ "agent": _b.display_name,
+ "provider": _b.provider,
+ "key_status": "✅ provided" if PROVIDER_KEYS.get(_b.key_field) else "❌ missing",
+ }
+ for _b in BACKENDS.values()
+ ]
+ agent_table = mo.ui.table(
+ _agent_rows,
+ selection="multi",
+ initial_selection=[_i for _i, _r in enumerate(_agent_rows) if _r["key_status"].startswith("✅")],
+ label="Select the coding agents to evaluate",
+ page_size=20,
+ )
+ run_eval_button = mo.ui.run_button(
+ label="Run selected evaluation",
+ tooltip="Provision a sandbox per agent, solve tasks inside it, score in Weave.",
+ kind="success",
+ )
+ model_picker_modal = mo.vstack(
+ [
+ mo.md(
+ """
+ ### Evaluation controls
+
+ Check the agents you want to evaluate, then click **Run selected
+ evaluation**. Agents whose provider key is missing will be skipped.
+ """
+ ),
+ agent_table,
+ run_eval_button,
+ ]
+ )
+
+ model_picker_modal
+ return agent_table, run_eval_button
+
+
+@app.cell(hide_code=True)
+def _(agent_table, mo):
+ selected_agent_ids = [
+ _row["agent_id"] for _row in (agent_table.value or []) if _row.get("agent_id")
+ ]
+ _label = ", ".join(selected_agent_ids) if selected_agent_ids else "No agents selected"
+
+ mo.md(
+ f"""
+ **Selected agent(s):** `{_label}`
+ **Number of agent runs queued:** `{len(selected_agent_ids)}`
+ """
+ )
+ return (selected_agent_ids,)
+
+
+@app.cell(hide_code=True)
+async def _(
+ BACKENDS,
+ CodeScorer,
+ PROVIDER_KEYS,
+ SandboxAgentModel,
+ dataset,
+ dataset_name,
+ mo,
+ run_eval_button,
+ selected_agent_ids,
+ selected_benchmark_tasks,
+ weave,
+):
+ import os as _os
+
+ # Weave evaluates rows concurrently (WEAVE_PARALLELISM). Unlike a hosted-API
+ # eval, here every concurrent predict is a FULL agent process (Node/Rust) sharing
+ # ONE agent sandbox, so the old default of 20 starved CPU/RAM and OOM-killed
+ # agents — surfacing as empty-output runs and 180s timeouts (notably Claude).
+ # Cap the default at a sandbox-friendly 4; raise WEAVE_PARALLELISM if your agent
+ # sandbox has more headroom. Harness agents still pin eval_parallelism=1 (shared
+ # per-sandbox state deadlocks). We set this PER AGENT right before evaluate().
+ _DEFAULT_PARALLELISM = _os.environ.get("WEAVE_PARALLELISM", "4")
+
+ async def _run_evaluations():
+ _lines = []
+ _created = []
+ try:
+ for _agent_id in selected_agent_ids:
+ _backend = BACKENDS[_agent_id]
+ _key = PROVIDER_KEYS.get(_backend.key_field)
+ if not _key:
+ _lines.append(f"⚠️ Skipped **{_backend.display_name}** — missing `{_backend.key_field}`.")
+ continue
+
+ _model = SandboxAgentModel(agent_id=_agent_id)
+ _model._api_key = _key
+
+ # Per-task budget: harness agents (OpenClaw) override the default.
+ if _backend.run_timeout_seconds:
+ _model.run_timeout_seconds = _backend.run_timeout_seconds
+ # Sandbox lifetime must outlast the WHOLE eval. Parallel agents finish
+ # fast, but serial agents (eval_parallelism=1) run tasks back-to-back,
+ # so a fixed cap expires mid-eval and the sandbox vanishes (NOT_FOUND).
+ # Scale the lifetime by task count for those, keeping the default floor.
+ if _backend.eval_parallelism == 1:
+ _per_task = _model.run_timeout_seconds + 60 # + scorer/overhead
+ _model.max_lifetime_seconds = max(
+ _model.max_lifetime_seconds,
+ _model.install_timeout_seconds
+ + len(selected_benchmark_tasks) * _per_task,
+ )
+
+ _created.append(_model)
+ # Provision now (traced via the agent_setup op) so install errors
+ # surface here and don't skew the first row's latency. setup() returns
+ # a status dict instead of raising.
+ _setup = _model.ensure_ready()
+ if _setup.get("status") != "ready":
+ _lines.append(
+ f"❌ **{_backend.display_name}** setup failed: {_setup.get('error')}"
+ )
+ continue
+ _lines.append(
+ f"🟢 **{_backend.display_name}** sandbox `{_setup.get('sandbox_id')}` ready."
+ )
+
+ # Pin concurrency for this agent (harness agents -> 1).
+ _par = _backend.eval_parallelism
+ _os.environ["WEAVE_PARALLELISM"] = str(_par) if _par else _DEFAULT_PARALLELISM
+ _lines.append(
+ f" ↳ concurrency `{_os.environ['WEAVE_PARALLELISM']}`, "
+ f"per-task timeout `{_model.run_timeout_seconds}s`, "
+ f"sandbox lifetime `{_model.max_lifetime_seconds}s`."
+ )
+
+ _scorer = CodeScorer(name="code_scorer")
+ _evaluation = weave.Evaluation(
+ name="agent-code-eval",
+ dataset=dataset,
+ scorers=[_scorer],
+ evaluation_name=f"{_agent_id}_agent_eval",
+ )
+ _results = await _evaluation.evaluate(model=_model)
+ print(f"Agent: {_agent_id}")
+ print(_results)
+ print("-" * 100)
+ _lines.append(f"✅ **{_backend.display_name}** evaluation finished.")
+ finally:
+ for _m in _created:
+ _m.stop()
+ return _lines
+
+ if run_eval_button.value and selected_agent_ids and selected_benchmark_tasks:
+ _out_lines = await _run_evaluations()
+ _evaluation_status = mo.md(
+ "### Run summary\n\n" + "\n\n".join(_out_lines) +
+ f"\n\nDataset **`{dataset_name}`**. Open W&B Weave to inspect traces and metrics."
+ )
+ elif run_eval_button.value and not selected_agent_ids:
+ _evaluation_status = mo.md("⚠️ No agents selected. Pick at least one agent above, then run again.")
+ elif run_eval_button.value and not selected_benchmark_tasks:
+ _evaluation_status = mo.md("⚠️ No benchmark tasks selected. Pick at least one task above, then run again.")
+ else:
+ _evaluation_status = mo.md(
+ "⏸️ Evaluation is ready but has not been launched. Choose agents and tasks, then click **Run selected evaluation**."
+ )
+
+ _evaluation_status
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 6. Lifecycle, discovery, and cleanup
+
+ /// admonition | Find and stop sandboxes
+ type: info
+
+ Both agent and scorer sandboxes are tagged `agent-sandbox-eval`. The run loop
+ tears down agent sandboxes automatically, but if a run is interrupted you can
+ discover and stop any survivors with `Sandbox.list` + `stop`.
+
+ ```python
+ running = Sandbox.list(tags=["agent-sandbox-eval"], include_stopped=True).result()
+ sb = Sandbox.from_id(running[0].sandbox_id).result()
+ print(sb.get_status())
+ sb.stop(missing_ok=True).result()
+ ```
+ ///
+ """)
+ return
+
+
+@app.cell
+def _(mo):
+ lifecycle_btn = mo.ui.run_button(label="List my agent-eval sandboxes", kind="neutral")
+ lifecycle_btn
+ return (lifecycle_btn,)
+
+
+@app.cell(hide_code=True)
+def _(API_KEY, Sandbox, lifecycle_btn, mo):
+ mo.stop(not API_KEY, mo.md("_Connect at the top first._"))
+ mo.stop(not lifecycle_btn.value, mo.md("_Press the button to list tagged sandboxes._"))
+
+ try:
+ _sandboxes = Sandbox.list(
+ tags=["agent-sandbox-eval"], include_stopped=True
+ ).result()
+ _rows = [
+ f"- `{_s.sandbox_id}` — status `{_s.status}`"
+ for _s in _sandboxes[:20]
+ ]
+ _body = "\n".join(_rows) or "_No tagged sandboxes yet — run an evaluation above first._"
+ _out = mo.callout(
+ mo.md(
+ f"**Sandboxes tagged `agent-sandbox-eval`:** {len(_sandboxes)}\n\n{_body}"
+ ),
+ kind="success",
+ )
+ except Exception as _e:
+ _out = mo.callout(
+ mo.md(f"⚠️ Listing unavailable here: `{type(_e).__name__}: {_e}`"),
+ kind="warn",
+ )
+ _out
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## What you just did
+
+ /// admonition | Recap
+ type: success
+
+ - Connected to W&B and supplied provider keys for headless agent auth
+ - Defined coding agents (Codex, Claude, OpenClaw, Hermes) behind an `AgentBackend` seam
+ - Wrapped each agent in a `SandboxAgentModel` that **provisions a sandbox at
+ init**, installs + authenticates the CLI, and drives it inside the sandbox
+ on every `predict`
+ - `CodeScorer` to verify solutions in fresh, network-isolated sandboxes
+ - Ran a per-agent `weave.Evaluation` and traced every generation + score
+ - Tore down agent sandboxes and listed survivors for cleanup
+ ///
+
+ /// details | Where to next
+ type: info
+
+ - [Serverless Sandboxes docs](https://docs.wandb.ai/sandboxes)
+ - [W&B Weave docs](https://weave-docs.wandb.ai/)
+ ///
+ """)
+ return
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/sandboxes/serverless-sandboxes-tutorial.py b/sandboxes/serverless-sandboxes-tutorial.py
new file mode 100644
index 0000000..a70040c
--- /dev/null
+++ b/sandboxes/serverless-sandboxes-tutorial.py
@@ -0,0 +1,916 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+# "cwsandbox==0.24.0",
+# "marimo>=0.23.8",
+# "openai==2.46.0",
+# "wandb[sandbox]==0.27.0",
+# "weave==0.52.38",
+# ]
+# ///
+
+import marimo
+
+__generated_with = "0.23.6"
+app = marimo.App(
+ width="medium",
+ app_title="Serverless Sandbox Tutorial",
+ css_file="/usr/local/_marimo/custom.css",
+ auto_download=["html"],
+)
+
+
+@app.cell
+def _():
+ import os
+ import time
+ import weave
+ import openai
+ import marimo as mo
+ from wandb.sandbox import (
+ Sandbox,
+ SandboxDefaults,
+ ResourceOptions,
+ SandboxCommandTimeoutError,
+ SandboxExecutionError,
+ SandboxError,
+ )
+
+ return (
+ ResourceOptions,
+ Sandbox,
+ SandboxCommandTimeoutError,
+ SandboxDefaults,
+ SandboxError,
+ SandboxExecutionError,
+ mo,
+ openai,
+ os,
+ time,
+ weave,
+ )
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.vstack(
+ [
+ mo.md(
+ r"""
+ # Evaluating Code-Generation Models with Serverless Sandbox, Inference, and Weave
+
+ /// admonition | About This Notebook
+ type: info
+
+ This marimo notebook walks through an end-to-end code-evaluation workflow for
+ hosted LLMs. It uses three complementary products:
+
+ - **Serverless Inference** to call hosted code-generation models through an OpenAI-compatible API.
+ - **Serverless Sandbox** to execute generated Python safely in isolated environments.
+ - **W&B Weave** to trace model calls, score outputs, and compare evaluation runs.
+
+ The flow is: a model generates a Python function for each benchmark task, the
+ function runs against deterministic tests inside a sandbox, the pass/fail result
+ becomes a score, and every step is traced to Weave for side-by-side comparison.
+
+ _If you are running this notebook in edit mode, make sure you start by running all cells._
+ ///
+ """
+ ),
+ mo.md(
+ r"""
+ /// details | Prerequisites
+ type: info
+
+ Before you begin, make sure you have:
+
+ - **A W&B account** — sign up free at [wandb.ai](https://wandb.ai) if you don't have one.
+ - **A W&B API key** *(required)* — generate one at [wandb.ai/authorize](https://wandb.ai/authorize) and paste it into the Connect form below. It authenticates both Inference and Weave logging.
+ - **Inference access** — needed to call the hosted models in the picker. See [Serverless Inference](https://docs.wandb.ai/guides/inference/).
+
+ Use the controls below to connect, choose benchmark difficulties, select one or
+ more models, and launch a reproducible evaluation run.
+ ///
+ """
+ ),
+ mo.md(
+ r"""
+ /// details | Table of Contents
+ type: info
+
+ - [**Connect W&B services**](#1-connect-wb-services) - Authenticate and initialize Weave
+ - [**Define the code-generation agent**](#2-define-the-code-generation-agent) - Wrap a hosted model as a Weave `Model`
+ - [**Score generated code safely in Serverless Sandbox**](#3-score-generated-code-safely-in-serverless-sandbox) - Run untrusted code in isolation
+ - [**Benchmark tasks**](#4-benchmark-tasks) - Build a versioned Weave `Dataset`
+ - [**Pick models and launch an evaluation**](#5-pick-models-and-launch-an-evaluation) - Run a multi-model `weave.Evaluation`
+ - [**Lifecycle, discovery, and cleanup**](#6-lifecycle-discovery-and-cleanup) - Find and stop sandboxes
+ ///
+ """
+ ),
+ ]
+ )
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 1. Connect W&B services
+
+ /// admonition | Connect and initialize Weave
+ type: info
+
+ Fill in the **Connect** form above with your W&B entity (team or username),
+ a project name, and your API key, then press **Connect**. The entity and
+ project default to the tutorial values: change them to log into your own
+ workspace. The key is stored in `WANDB_API_KEY` and used to call
+ `weave.init("/")`.
+
+ The form gates the rest of the notebook: the agent, scorer, and evaluation
+ cells stay paused until you connect. Once connected, every model call,
+ generated solution, scorer result, and evaluation summary is logged under
+ your project, and a link to the Weave dashboard appears in the success
+ callout above.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ # ---------- 1. Setup W&B ----------
+ wandb_connect_form = (
+ mo.md("""
+ - W&B entity *(team)*: {entity}
+ - W&B project: {project}
+ - W&B API key *(required)*: {api_key}
+ """)
+ .batch(
+ entity=mo.ui.text(placeholder="your-wandb-entity" , full_width=True),
+ project=mo.ui.text(value="serverless-sandbox-tutorial", full_width=True),
+ api_key=mo.ui.text(kind="password", placeholder="from wandb.ai/authorize", full_width=True),
+ )
+ .form(submit_button_label="Connect", bordered=False)
+ )
+ wandb_connect_form
+ return (wandb_connect_form,)
+
+
+@app.cell(hide_code=True)
+def _(mo, os, wandb_connect_form, weave):
+ _v = wandb_connect_form.value or {}
+ ENTITY = _v.get("entity")
+ PROJECT = _v.get("project")
+ API_KEY = _v.get("api_key")
+ mo.stop(
+ not (ENTITY and PROJECT and API_KEY),
+ mo.md("_Fill in the form above and press **Connect**._"),
+ )
+
+ os.environ["WANDB_API_KEY"] = API_KEY
+ weave.init(f"{ENTITY}/{PROJECT}")
+ weave_url = f"https://wandb.ai/{ENTITY}/{PROJECT}/weave"
+
+ mo.callout(
+ mo.md(
+ f"✅ **Connected** — logging to `{ENTITY}/{PROJECT}`. "
+ f"[Open Weave dashboard]({weave_url})"
+ ),
+ kind="success",
+ )
+ return API_KEY, ENTITY, PROJECT
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ wandb_product_tabs = mo.ui.tabs(
+ {
+ "Sandbox": mo.md(
+ f"""
+ ### Serverless Sandbox
+
+ The sandbox executes each generated Python solution in an isolated environment. This keeps evaluation safer while still letting the scorer run real assertions against generated code.
+
+ It's a full remote-execution platform: file I/O (`write_file`/`read_file`), CPU/memory resource control, env vars and team Secrets, networking/egress, live log streaming, parallel `Session`s with remote functions, and tag-based lifecycle management. Serverless Sandbox runs CPU workloads (no GPU). The code scorer in Step 3 exercises these directly.
+ """
+ ),
+ "Inference": mo.md(
+ f"""
+ ### Serverless Inference
+
+ This notebook uses Serverless Inference through an OpenAI-compatible client. Each benchmark prompt is sent to a hosted model with a low-temperature generation setting so the evaluation is repeatable and easy to compare across model providers.
+ """
+ ),
+ "Weave": mo.md(
+ f"""
+ ### W&B Weave
+
+ Weave tracks the full evaluation lifecycle: model inputs, generated code, scorer outputs, pass/fail metrics, and latency. That makes it easy to inspect individual failures and compare runs across models.
+ """
+ )
+ }
+ )
+
+ wandb_product_tabs
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 2. Define the code-generation agent
+
+ /// admonition | LeetCodeAgent
+ type: info
+
+ `LeetCodeAgent` wraps a Serverless Inference model behind a Weave `Model` interface. Given a task name, spec, and tests, its `predict` method returns the model's raw solution string, the output drops straight into the sandbox scorer. `predict` is a `@weave.op`, so each
+ generation is traced with its inputs, output, and latency.
+ ///
+ """)
+ return
+
+
+@app.cell
+def _(API_KEY, ENTITY, PROJECT, openai, weave):
+ #---------- 1.1 Define Function Agent ----------
+ SYSTEM_PROMPT = weave.StringPrompt(
+ """You are a precise Python coder.
+ Given a function specification, return ONLY the function definition.
+ - No markdown code fences.
+ - No explanation.
+ - Use the exact function name and signature requested.
+ """
+ )
+ class LeetCodeAgent(weave.Model):
+ model_name: str
+ system_prompt: weave.StringPrompt = SYSTEM_PROMPT
+ max_tokens: int = 1000
+ temperature: float = 0.0
+
+ @property
+ def client(self):
+ return openai.OpenAI(
+ base_url="https://api.inference.wandb.ai/v1",
+ api_key=API_KEY,
+ project=f"{ENTITY}/{PROJECT}",
+ )
+
+ @weave.op(name="generate_solution", kind="llm")
+ def predict(self, name: str, spec: str, tests: list) -> str:
+ resp = self.client.chat.completions.create(
+ model=self.model_name,
+ messages=[
+ {"role": "system", "content": SYSTEM_PROMPT.content},
+ {"role": "user", "content": spec},
+ ],
+ temperature=self.temperature,
+ max_tokens=self.max_tokens,
+ )
+ msg = resp.choices[0].message
+
+ # Standard content
+ if msg.content:
+ return msg.content.strip()
+
+ # Reasoning/thinking models (e.g. Qwen, DeepSeek-R1) that put output in reasoning_content
+ reasoning = getattr(msg, "reasoning_content", None)
+ if reasoning:
+ return reasoning.strip()
+
+ # Tool-call responses — extract the first tool call argument
+ if msg.tool_calls:
+ return msg.tool_calls[0].function.arguments.strip()
+
+ return "failed"
+
+ return (LeetCodeAgent,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 3. Score generated code safely in Serverless Sandbox
+
+ /// admonition | CodeScorer
+ type: info
+
+ `CodeScorer` is where the sandbox does its real work. It wraps each task's
+ `(input, expected)` pairs in a small harness, runs the model's solution inside
+ a fresh sandbox, and returns a structured `{passed, error, sandbox_latency}`
+ result for Weave. Running untrusted model output in an isolated sandbox is the
+ whole point so the evaluation stays safe.
+
+ The scorer sets these Sandbox defaults:
+
+ - **`SandboxDefaults`** (defined just below) — one immutable config applied to
+ every scorer sandbox: container image, `tags` (used for cleanup in the final
+ step), injected **environment variables**, and **CPU/memory `ResourceOptions`**.
+ - **`write_file` + `read_file`** — the harness script is written in with
+ `write_file`; instead of scraping stdout, the script writes a JSON verdict to
+ `/tmp/result.json` that the scorer pulls back with `read_file`.
+ - **Native `timeout_seconds`** — the SDK enforces the 5s budget directly, so
+ there's no `timeout 5` shell wrapper.
+ - **Network isolation by default** — sandboxes have no egress unless you ask
+ for it, so model code can't phone home while being scored.
+ - **Typed errors** — `SandboxCommandTimeoutError` is a *real* failed completion
+ (no retry); `SandboxExecutionError` / `SandboxError` are infra issues the
+ backoff loop retries.
+ ///
+ """)
+ return
+
+
+@app.cell
+def _(ResourceOptions, SandboxDefaults):
+ # Shared sandbox configuration. SandboxDefaults is an immutable config object
+ # applied to every sandbox the scorer creates — container image, tags (for
+ # later discovery/cleanup via Sandbox.list), injected env vars, and CPU/memory
+ # limits. Setting it once here is the idiomatic alternative to passing the same
+ # arguments on every Sandbox.run() call.
+ SANDBOX_DEFAULTS = SandboxDefaults(
+ container_image="python:3.11",
+ tags=("wandb-sandbox-tutorial", "code-eval"),
+ environment_variables={"PYTHONUNBUFFERED": "1"},
+ resources=ResourceOptions(
+ requests={"cpu": "250m", "memory": "256Mi"},
+ limits={"cpu": "1", "memory": "512Mi"},
+ ),
+ )
+ return (SANDBOX_DEFAULTS,)
+
+
+@app.cell
+def _(
+ SANDBOX_DEFAULTS,
+ Sandbox,
+ SandboxCommandTimeoutError,
+ SandboxError,
+ SandboxExecutionError,
+ time,
+ weave,
+):
+ # ---------- 1.3 Define Sandbox-based scorer ----------
+ import json
+
+ class CodeScorer(weave.Scorer):
+
+ @weave.op(name="code_scorer", kind="scorer")
+ def score(self, name: str, spec: str, tests: list, output: str) -> dict:
+ start_time = time.time()
+ asserts = "\n".join(
+ f" assert {name}(*{args!r}) == {expected!r}, 'failed on input {args!r}'"
+ for args, expected in tests
+ )
+ # The harness runs every assertion, then writes a structured verdict
+ # to a file, which we pull back out with read_file.
+ script = (
+ f"{output}\n\n"
+ "import json\n"
+ "_passed, _error = True, ''\n"
+ "try:\n"
+ f"{asserts}\n"
+ "except Exception as _e:\n"
+ " _passed, _error = False, repr(_e)\n"
+ "with open('/tmp/result.json', 'w') as _f:\n"
+ " json.dump({'passed': _passed, 'error': _error}, _f)\n"
+ )
+
+ last_exc = None
+ for attempt in range(3):
+ sb = None
+ try:
+ sb = Sandbox.run(defaults=SANDBOX_DEFAULTS)
+ # write_file ships the script in;
+ # read_file pulls the verdict back out
+ sb.write_file("/tmp/t.py", script.encode()).result()
+ proc = sb.exec(
+ ["python", "/tmp/t.py"], timeout_seconds=5
+ ).result()
+ try:
+ verdict = json.loads(
+ sb.read_file("/tmp/result.json").result().decode()
+ )
+ passed = bool(verdict.get("passed"))
+ error = str(verdict.get("error", ""))
+ except Exception:
+ passed = False
+ error = (proc.stderr or "no result file written")
+ end_time = time.time()
+ return {
+ "passed": passed,
+ "error": error[-300:],
+ "sandbox_latency": (end_time - start_time),
+ }
+ except SandboxCommandTimeoutError:
+ end_time = time.time()
+ return {
+ "passed": False,
+ "error": "execution timed out after 5s",
+ "sandbox_latency": (end_time - start_time),
+ }
+ except (SandboxExecutionError, SandboxError) as e:
+ last_exc = e
+ time.sleep(2 ** attempt)
+ finally:
+ if sb is not None:
+ try:
+ sb.stop(missing_ok=True).result()
+ except Exception:
+ pass
+ end_time = time.time()
+ return {"passed": False, "error": f"Sandbox unavailable after 3 attempts: {last_exc}", "sandbox_latency": (end_time - start_time)}
+
+ return (CodeScorer,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ /// details | Also possible (beyond this eval)
+ type: info
+
+ A few sandbox capabilities aren't needed to score code, but are one argument
+ away when your use case calls for them:
+
+ - **Team Secrets** — inject credentials from your W&B `wandb-team-secrets`
+ store without hardcoding them: `Sandbox.run(..., secrets=[Secret(name="OPENAI_API_KEY", env_var="OPENAI_API_KEY")])`.
+ - **Outbound egress / serving a port** — the scorer keeps sandboxes isolated,
+ but a task that needs the network can opt in:
+ `Sandbox.run(..., network=NetworkOptions(egress_mode="internet"))`, or expose
+ a port with `ingress_mode` + `exposed_ports` to serve a model.
+ - **Live log streaming** — for long-running jobs, follow output as it happens
+ with `for line in sb.stream_logs(follow=True): ...`.
+ - **Snapshots & lifetime caps** — `sb.stop(snapshot_on_stop=True)` or
+ `Sandbox.run(..., max_lifetime_seconds=300)` to bound resource usage.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 4. Benchmark tasks
+
+ /// admonition | Build a versioned Weave Dataset
+ type: info
+
+ The benchmark mixes easy, medium, and hard LeetCode-style prompts. Each row
+ has a function name, a natural-language spec, and deterministic
+ `(input, expected)` tests that the sandbox scorer converts into Python
+ assertions. Select the exact tasks you want in the table below, the
+ selection updates live and builds a versioned `Dataset` in Weave, so each evaluation run
+ is tied to an explicit set of tasks.
+ ///
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _():
+ # ---------- 1.2 Define Ground truth Dataset ----------
+ # Each task has a name, a natural-language spec, and a list of (input, expected) pairs.
+ TASKS = [
+ # --- Easy ---
+ {
+ "name": "add",
+ "spec": "Write a function `add(a, b)` that returns the sum of two numbers.",
+ "tests": [((2, 3), 5), ((-1, 1), 0), ((0, 0), 0)],
+ },
+ {
+ "name": "reverse_string",
+ "spec": "Write a function `reverse_string(s)` that returns the reverse of string s.",
+ "tests": [(("hello",), "olleh"), (("",), ""), (("a",), "a")],
+ },
+ {
+ "name": "fizzbuzz",
+ "spec": "Write a function `fizzbuzz(n)` returning 'Fizz' if n is divisible by 3, 'Buzz' if by 5, 'FizzBuzz' if by both, else str(n).",
+ "tests": [((3,), "Fizz"), ((5,), "Buzz"), ((15,), "FizzBuzz"), ((7,), "7")],
+ },
+ # --- Medium ---
+ {
+ "name": "is_prime",
+ "spec": "Write a function `is_prime(n)` that returns True iff n is a prime number. n is a positive integer.",
+ "tests": [((2,), True), ((4,), False), ((17,), True), ((1,), False)],
+ },
+ {
+ "name": "second_largest",
+ "spec": "Write a function `second_largest(nums)` returning the second-largest distinct value in a list of ints. Assume len(nums) >= 2 and at least 2 distinct values.",
+ "tests": [(([1, 2, 3],), 2), (([5, 5, 4, 4, 3],), 4), (([-1, -2, -3],), -2)],
+ },
+ {
+ "name": "flatten",
+ "spec": "Write a function `flatten(lst)` that takes a possibly nested list of ints and returns a flat list of all ints in order.",
+ "tests": [
+ (([1, [2, [3, 4]], 5],), [1, 2, 3, 4, 5]),
+ (([],), []),
+ (([[1], [2, [3]]],), [1, 2, 3]),
+ ],
+ },
+ {
+ "name": "group_anagrams",
+ "spec": "Write a function `group_anagrams(words)` that groups a list of strings into lists of anagrams. Each group must be sorted alphabetically internally. The returned list of groups must be sorted by the first element of each group.",
+ "tests": [
+ ((["eat", "tea", "tan", "ate", "nat", "bat"],), [["ate", "eat", "tea"], ["bat"], ["nat", "tan"]]),
+ (([""],), [[""]]),
+ ((["a"],), [["a"]]),
+ ],
+ },
+ {
+ "name": "longest_common_subsequence",
+ "spec": "Write a function `longest_common_subsequence(s1, s2)` that returns the length of the longest common subsequence of strings s1 and s2.",
+ "tests": [
+ (("abcde", "ace"), 3),
+ (("abc", "abc"), 3),
+ (("abc", "def"), 0),
+ ],
+ },
+ # --- Hard ---
+ {
+ "name": "min_coins",
+ "spec": "Write a function `min_coins(coins, amount)` that returns the minimum number of coins needed to make up the given amount using the given coin denominations. Return -1 if it is not possible.",
+ "tests": [
+ (([1, 5, 11], 15), 3),
+ (([2], 3), -1),
+ (([1, 2, 5], 11), 3),
+ ],
+ },
+ {
+ "name": "longest_palindrome",
+ "spec": "Write a function `longest_palindrome(s)` that returns the longest palindromic substring of s. If there are ties, return the one that starts earliest.",
+ "tests": [
+ (("babad",), "bab"),
+ (("cbbd",), "bb"),
+ (("a",), "a"),
+ (("racecar",), "racecar"),
+ ],
+ },
+ {
+ "name": "word_break",
+ "spec": "Write a function `word_break(s, word_dict)` that returns True if the string s can be segmented into a space-separated sequence of one or more words from word_dict (a list of strings).",
+ "tests": [
+ (("leetcode", ["leet", "code"]), True),
+ (("applepenapple", ["apple", "pen"]), True),
+ (("catsandog", ["cats", "dog", "sand", "and", "cat"]), False),
+ ],
+ },
+ {
+ "name": "serialize_tree",
+ "spec": """Write a function `serialize_tree(root)` that serializes a binary tree to a string and a function `deserialize_tree(data)` that deserializes it back. A tree node is represented as a list [val, left, right] where left and right are either None or another such list. The round-trip must be lossless: deserialize_tree(serialize_tree(root)) == root.""",
+ "tests": [
+ (([1, [2, None, None], [3, [4, None, None], [5, None, None]]],), [1, [2, None, None], [3, [4, None, None], [5, None, None]]]),
+ ((None,), None),
+ (([1, None, [2, None, [3, None, None]]],), [1, None, [2, None, [3, None, None]]]),
+ ],
+ },
+ ]
+ return (TASKS,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ Check the rows you want below. By default every task is selected; uncheck
+ any you want to exclude, or clear them all and pick a focused subset. The
+ dataset summary underneath updates as you change the selection.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(TASKS, mo):
+ def _difficulty_for(index: int) -> str:
+ return "Easy" if index < 3 else "Medium" if index < 8 else "Hard"
+
+ _all_task_rows = [
+ {
+ "difficulty": _difficulty_for(_i),
+ "function": _t["name"],
+ "test_count": len(_t["tests"]),
+ "spec": _t["spec"],
+ }
+ for _i, _t in enumerate(TASKS)
+ ]
+ task_table = mo.ui.table(
+ _all_task_rows,
+ selection="multi",
+ initial_selection=list(range(len(_all_task_rows))),
+ label="Select the benchmark tasks to include in this evaluation",
+ page_size=20,
+ )
+ task_table
+ return (task_table,)
+
+
+@app.cell(hide_code=True)
+def _(TASKS, mo, task_table, weave):
+ def _difficulty_for(index: int) -> str:
+ return "Easy" if index < 3 else "Medium" if index < 8 else "Hard"
+
+ _difficulty_order = ["Easy", "Medium", "Hard"]
+ _task_by_name = {_t["name"]: (_i, _t) for _i, _t in enumerate(TASKS)}
+
+ _selected = sorted(
+ (
+ _task_by_name[_row["function"]]
+ for _row in (task_table.value or [])
+ if _row.get("function") in _task_by_name
+ ),
+ key=lambda _pair: _pair[0],
+ )
+ selected_benchmark_tasks = [_task for _, _task in _selected]
+ _present = [
+ _d for _d in _difficulty_order
+ if _d in {_difficulty_for(_i) for _i, _ in _selected}
+ ]
+
+ if not selected_benchmark_tasks:
+ _suffix = "none"
+ elif len(selected_benchmark_tasks) == len(TASKS):
+ _suffix = "all"
+ else:
+ _suffix = "_".join(_d.lower() for _d in _present) + f"_{len(selected_benchmark_tasks)}tasks"
+ dataset_name = f"tasks_{_suffix}"
+
+ dataset = (
+ weave.Dataset(name=dataset_name, rows=selected_benchmark_tasks)
+ if selected_benchmark_tasks
+ else None
+ )
+
+ if selected_benchmark_tasks:
+ _summary = mo.callout(
+ mo.md(
+ f"**Weave dataset:** `{dataset_name}` \n"
+ f"**Selected tasks:** `{len(selected_benchmark_tasks)}` of `{len(TASKS)}` "
+ f"({', '.join(_present)})"
+ ),
+ kind="success",
+ )
+ else:
+ _summary = mo.callout(
+ mo.md("No tasks selected — check one or more rows in the table above to build a dataset."),
+ kind="warn",
+ )
+ _summary
+ return dataset, dataset_name, selected_benchmark_tasks
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(f"""
+ ---
+ ## 5. Pick models and launch an evaluation
+
+ /// admonition | Run a multi-model weave.Evaluation
+ type: info
+
+ Pick one hosted model for a fast debug run or the full set for a side-by-side
+ comparison, then press **Run selected evaluation**. The run button keeps
+ expensive model calls from firing automatically whenever you open or edit the
+ notebook. Each selected model is wrapped in a `LeetCodeAgent`, scored by
+ `CodeScorer` against the dataset, and logged as a `weave.Evaluation` you can
+ open in the Weave dashboard.
+
+ **Parallel sandboxes:** `weave.Evaluation` scores dataset rows
+ concurrently, and since `CodeScorer` spins up a sandbox per call, you're
+ running many sandboxes in parallel. If you ever own the loop yourself
+ (RL rollouts, a custom harness), a `Session` gives you the same fan-out
+ explicitly —> `@session.function()` + `.map()` to launch, and
+ `wandb.sandbox.wait(refs, num_returns=1)` to harvest results as they finish.
+ ///
+ """)
+ return
+
+
+@app.cell
+def _():
+ models_list = [
+ "meta-llama/Llama-3.3-70B-Instruct",
+ "deepseek-ai/DeepSeek-V4-Flash",
+ "MiniMaxAI/MiniMax-M2.5",
+ "moonshotai/Kimi-K2.6",
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct",
+
+ ]
+ return (models_list,)
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ Check rows in the table below to choose between a full model comparison and a single-model debug run.
+ """)
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo, models_list):
+ _model_rows = [
+ {"provider": _m.split("/")[0], "model": _m}
+ for _m in models_list
+ ]
+ model_table = mo.ui.table(
+ _model_rows,
+ selection="multi",
+ initial_selection=list(range(len(_model_rows))),
+ label="Select the Serverless Inference models to evaluate",
+ page_size=20,
+ )
+ run_eval_button = mo.ui.run_button(
+ label="Run selected evaluation",
+ tooltip="Run the selected Serverless Inference model(s), score generated code in Sandbox, and log results to Weave.",
+ kind="success",
+ )
+ model_picker_modal = mo.vstack(
+ [
+ mo.md(
+ f"""
+ ### Evaluation controls
+
+ Check the models you want to evaluate, then click **Run selected evaluation**. Select a single row for a fast debug run, or all rows for a full side-by-side comparison.
+ """
+ ),
+ model_table,
+ run_eval_button,
+ ]
+ )
+
+ model_picker_modal
+ return model_table, run_eval_button
+
+
+@app.cell(hide_code=True)
+def _(mo, model_table):
+ selected_model_names = [
+ _row["model"] for _row in (model_table.value or []) if _row.get("model")
+ ]
+ _selected_model_label = ", ".join(selected_model_names) if selected_model_names else "No models selected"
+
+ mo.md(
+ f"""
+ **Selected evaluation target(s):** `{_selected_model_label}`
+ **Number of model runs queued:** `{len(selected_model_names)}`
+ """
+ )
+ return (selected_model_names,)
+
+
+@app.cell(hide_code=True)
+async def _(
+ CodeScorer,
+ LeetCodeAgent,
+ dataset,
+ dataset_name,
+ mo,
+ run_eval_button,
+ selected_benchmark_tasks,
+ selected_model_names,
+ weave,
+):
+ async def _run_evaluations():
+ for name in selected_model_names:
+ _model = LeetCodeAgent(model_name=name)
+ _code_scorer = CodeScorer(name="code_scorer")
+ evaluation = weave.Evaluation(
+ name="simple-code-eval",
+ dataset=dataset,
+ scorers=[_code_scorer],
+ evaluation_name=f"{name}_code_eval",
+ )
+ results = await evaluation.evaluate(model=_model)
+ print(f"Model: {name}")
+ print(results)
+ print("-" * 100)
+
+ if run_eval_button.value and selected_model_names and selected_benchmark_tasks:
+ await _run_evaluations()
+ _evaluation_status = mo.md(
+ f"""
+ ✅ Evaluation finished for **{len(selected_model_names)}** model run(s) on dataset **`{dataset_name}`**. Open W&B Weave to inspect traces, scorer outputs, and aggregate metrics.
+ """
+ )
+ elif run_eval_button.value and not selected_model_names:
+ _evaluation_status = mo.md(
+ f"""
+ ⚠️ No models selected. Pick at least one model above, then click **Run selected evaluation** again.
+ """
+ )
+ elif run_eval_button.value and not selected_benchmark_tasks:
+ _evaluation_status = mo.md(
+ f"""
+ ⚠️ No benchmark tasks selected. Pick at least one difficulty above, then click **Run selected evaluation** again.
+ """
+ )
+ else:
+ _evaluation_status = mo.md(
+ f"""
+ ⏸️ Evaluation is ready but has not been launched. Choose one or more models and benchmark difficulties, then click **Run selected evaluation**.
+ """
+ )
+
+ _evaluation_status
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## 6. Lifecycle, discovery, and cleanup
+
+ /// admonition | Find and stop sandboxes
+ type: info
+
+ Sandboxes are addressable resources. `tags` (set via `SandboxDefaults`) let
+ you find them later with `Sandbox.list`; `from_id` re-attaches to a running
+ one; `get_status` inspects state; and `stop` can snapshot or you can set a
+ hard `max_lifetime_seconds` so nothing leaks.
+
+ ```python
+ running = Sandbox.list(tags=["wandb-sandbox-tutorial"], include_stopped=True).result()
+ sb = Sandbox.from_id(running[0].sandbox_id).result()
+ print(sb.get_status())
+ sb.stop(snapshot_on_stop=True).result() # or Sandbox.run(..., max_lifetime_seconds=300)
+ ```
+ ///
+ """)
+ return
+
+
+@app.cell
+def _(mo):
+ lifecycle_btn = mo.ui.run_button(label="List my tutorial sandboxes", kind="neutral")
+ lifecycle_btn
+ return (lifecycle_btn,)
+
+
+@app.cell(hide_code=True)
+def _(API_KEY, Sandbox, lifecycle_btn, mo):
+ mo.stop(not API_KEY, mo.md("_Connect at the top first._"))
+ mo.stop(not lifecycle_btn.value, mo.md("_Press the button to list tagged sandboxes._"))
+
+ import traceback as _tb
+
+ try:
+ _sandboxes = Sandbox.list(
+ tags=["wandb-sandbox-tutorial"], include_stopped=True
+ ).result()
+ _rows = [
+ f"- `{_s.sandbox_id}` — status `{_s.status}`"
+ for _s in _sandboxes[:20]
+ ]
+ _body = "\n".join(_rows) or "_No tagged sandboxes yet — run the evaluation above first._"
+ _out = mo.callout(
+ mo.md(
+ f"**Sandboxes tagged `wandb-sandbox-tutorial`:** {len(_sandboxes)}\n\n{_body}"
+ ),
+ kind="success",
+ )
+ except Exception as _e:
+ _out = mo.callout(
+ mo.md(f"⚠️ Listing unavailable here: `{type(_e).__name__}: {_e}`"),
+ kind="warn",
+ )
+ _out
+ return
+
+
+@app.cell(hide_code=True)
+def _(mo):
+ mo.md(r"""
+ ---
+ ## What you just did
+
+ /// admonition | Recap
+ type: success
+
+ - Connected to W&B from a single form (entity, project, API key)
+ - Wrapped a hosted Serverless Inference model in a Weave `Model`
+ - Scored generated code safely inside Serverless Sandbox using
+ `SandboxDefaults`, `write_file`, native `timeout_seconds`, and typed errors
+ - Built a versioned Weave `Dataset` from a selectable task table
+ - Ran a multi-model `weave.Evaluation` and logged every trace, Weave scored
+ rows concurrently, so sandboxes ran in parallel
+ - Exercised the sandbox platform inside the scorer: `SandboxDefaults`, file
+ I/O (`write_file`/`read_file`), CPU/memory limits, network isolation, and
+ typed errors, then listed the tagged sandboxes for cleanup
+ ///
+
+ /// details | Where to next
+ type: info
+
+ - [Serverless Sandboxes docs](https://docs.wandb.ai/sandboxes)
+ - [Weave docs](https://weave-docs.wandb.ai/)
+ - [Weave Evaluations guide](https://weave-docs.wandb.ai/guides/core-types/evaluations)
+ ///
+ """)
+ return
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/uv.lock b/uv.lock
index 9c740cc..658612e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -16,6 +16,18 @@ resolution-markers = [
"python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'",
]
+[[package]]
+name = "abnf"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/f2/7b5fac50ee42e8b8d4a098d76743a394546f938c94125adbb93414e5ae7d/abnf-2.2.0.tar.gz", hash = "sha256:433380fd32855bbc60bc7b3d35d40616e21383a32ed1c9b8893d16d9f4a6c2f4", size = 197507, upload-time = "2023-03-17T18:26:24.577Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/30/95/f456ae7928a2f3a913f467d4fd9e662e295dd7349fc58b35f77f6c757a23/abnf-2.2.0-py3-none-any.whl", hash = "sha256:5dc2ae31a84ff454f7de46e08a2a21a442a0e21a092468420587a1590b490d1f", size = 39938, upload-time = "2023-03-17T18:26:22.608Z" },
+]
+
[[package]]
name = "aiohappyeyeballs"
version = "2.6.1"
@@ -212,6 +224,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
]
+[[package]]
+name = "backoff"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" },
+]
+
[[package]]
name = "basedpyright"
version = "1.38.2"
@@ -320,6 +341,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/32/76/cab7af7f16c0b09347f2ebe7ffda7101132f786acb767666dce43055faab/botocore_stubs-1.42.41-py3-none-any.whl", hash = "sha256:9423110fb0e391834bd2ed44ae5f879d8cb370a444703d966d30842ce2bcb5f0", size = 66759, upload-time = "2026-02-03T20:46:13.02Z" },
]
+[[package]]
+name = "cachetools"
+version = "7.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" },
+]
+
[[package]]
name = "cattrs"
version = "25.3.0"
@@ -421,6 +451,43 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
]
+[[package]]
+name = "chardet"
+version = "7.4.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/b6/9df434a8eeba2e6628c465a1dfa31034228ef79b26f76f46278f4ef7e49d/chardet-7.4.3.tar.gz", hash = "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56", size = 784800, upload-time = "2026-04-13T21:33:39.803Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/19/52/505c207f334d51e937cbaa27ff95776e16e2d120e13cbe491cd7b3a70b50/chardet-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09", size = 870747, upload-time = "2026-04-13T21:32:56.916Z" },
+ { url = "https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97", size = 853210, upload-time = "2026-04-13T21:32:58.309Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/99/f6a822ad1bde25a4c38dc3e770485e78e0893dfd871cd6e18ed3ea3a795e/chardet-7.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8", size = 873625, upload-time = "2026-04-13T21:32:59.606Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9", size = 883436, upload-time = "2026-04-13T21:33:01.351Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/63/0f43e3acf2c436fdb32a0f904aeb03a2904d2126eed34a042a194d235926/chardet-7.4.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4", size = 876589, upload-time = "2026-04-13T21:33:02.636Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578", size = 941866, upload-time = "2026-04-13T21:33:04.282Z" },
+ { url = "https://files.pythonhosted.org/packages/61/33/29de185079e6675c3f375546e30a559b7ddc75ce972f18d6e566cd9ea4eb/chardet-7.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971", size = 874870, upload-time = "2026-04-13T21:33:05.977Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a", size = 854859, upload-time = "2026-04-13T21:33:07.381Z" },
+ { url = "https://files.pythonhosted.org/packages/36/21/edb36ad5dfa48d7f8eed97ab43931ecdaa8c15166c21b1d614967e49d681/chardet-7.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235", size = 875032, upload-time = "2026-04-13T21:33:08.741Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/59/a32a241d861cf180853a11c8e5a67641cb1b2af13c3a5ccce83ec07e2c9f/chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb", size = 888283, upload-time = "2026-04-13T21:33:10.213Z" },
+ { url = "https://files.pythonhosted.org/packages/87/2e/e1ee6a77abf3782c00e05b89c4d4328c8353bf9500661c4348df1dd68614/chardet-7.4.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d2879598bc220689e8ce509fe9c3f37ad2fca53a36be9c9bd91abdd91dd364f", size = 879974, upload-time = "2026-04-13T21:33:11.448Z" },
+ { url = "https://files.pythonhosted.org/packages/32/60/fca69c534602a7ced04280c952a246ad1edde2a6ca3a164f65d32ac41fe7/chardet-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101", size = 943973, upload-time = "2026-04-13T21:33:12.756Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/43/79ac9b4db5bc87020c9dbc419125371d80882d1d197e9c4765ba8682b605/chardet-7.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e4486df251b8962e86ea9f139ca235aa6e0542a00f7844c9a04160afb99aa9", size = 873769, upload-time = "2026-04-13T21:33:14.002Z" },
+ { url = "https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a", size = 853991, upload-time = "2026-04-13T21:33:15.564Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/07/a29380ee0b215d23d77733b5ad60c5c0c7969650e080c667acdf9462040d/chardet-7.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:365135eaf37ba65a828f8e668eb0a8c38c479dcbec724dc25f4dfd781049c357", size = 874024, upload-time = "2026-04-13T21:33:16.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d", size = 887410, upload-time = "2026-04-13T21:33:18.368Z" },
+ { url = "https://files.pythonhosted.org/packages/63/1c/44a9a9e0c59c185a5d307ceaeee8768afa1558f0a24f7a4b5fa11b67586b/chardet-7.4.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9acd9988a93e09390f3cd231201ea7166c415eb8da1b735928990ffc05cb9fbb", size = 879269, upload-time = "2026-04-13T21:33:20.377Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131", size = 944155, upload-time = "2026-04-13T21:33:21.694Z" },
+ { url = "https://files.pythonhosted.org/packages/70/a8/bf0811d859e13801279a2ae64f37a408027b282f2047bc0001c75dd356ad/chardet-7.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d892d3dcd652fdef53e3d6327d39b17c0df40a899dfc919abaeb64c974497531", size = 872887, upload-time = "2026-04-13T21:33:23.328Z" },
+ { url = "https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4", size = 853964, upload-time = "2026-04-13T21:33:24.724Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/81/17fa103ea9caf5d325a5e4051ab2ba65996fd66baa60b81ee41af1f54e10/chardet-7.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ac3bf11c645734a1701a3804e43eabd98851838192267d08c353a834ab79fea", size = 876006, upload-time = "2026-04-13T21:33:26.098Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7", size = 887680, upload-time = "2026-04-13T21:33:27.49Z" },
+ { url = "https://files.pythonhosted.org/packages/40/c6/94a3c673327392652ee8bdea9a45bc8a5f5365197a7387d68f0eed007115/chardet-7.4.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:27cc23da03630cdecc9aa81a895aa86629c211f995cd57651f0fbc280717bf93", size = 879865, upload-time = "2026-04-13T21:33:29.052Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c", size = 939594, upload-time = "2026-04-13T21:33:31.391Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e0/d06e42fd6f02a58e5e227e5106587751cb38adcff0aaf949add744b78b6e/chardet-7.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c77867f0c1cb8bd819502249fcdc500364aedb07881e11b743726fa2148e7b6e", size = 889714, upload-time = "2026-04-13T21:33:32.772Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/ed/40d091954d48abea037baae6be8fb79905e5f78d34d12ea955132c7d8011/chardet-7.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cf1efeaf65a6ef2f5b9cc3a1df6f08ba2831b369ccaa4c7018eaf90aa757bb11", size = 872319, upload-time = "2026-04-13T21:33:34.427Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/77/82a46821dbfbdfe062710d2bf2ede13426304e3567a23c57d919c0c31630/chardet-7.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f3504c139a2ad544077dd2d9e412cd08b01786843d76997cd43bb6de311723c", size = 892021, upload-time = "2026-04-13T21:33:35.766Z" },
+ { url = "https://files.pythonhosted.org/packages/49/57/42d30c562bda5b4a839766c1aad8d5856b798ad2a1c3247b72a679afec94/chardet-7.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457f619882ba66327d4d8d14c6c342269bdb1e4e1c38e8117df941d14d351b04", size = 902509, upload-time = "2026-04-13T21:33:37.096Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/6c/0a40afdb50a0fe041ab95553b835a8160b6cf0e81edf2ae2fe9f5224cbf9/chardet-7.4.3-py3-none-any.whl", hash = "sha256:1173b74051570cf08099d7429d92e4882d375ad4217f92a6e5240ccfb26f231e", size = 626562, upload-time = "2026-04-13T21:33:38.559Z" },
+]
+
[[package]]
name = "charset-normalizer"
version = "3.4.4"
@@ -494,6 +561,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
+[[package]]
+name = "cint"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3e/c8/3ae22fa142be0bf9eee856e90c314f4144dfae376cc5e3e55b9a169670fb/cint-1.0.0.tar.gz", hash = "sha256:66f026d28c46ef9ea9635be5cb342506c6a1af80d11cb1c881a8898ca429fc91", size = 4641, upload-time = "2019-03-19T01:07:48.723Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/c2/898e59963084e1e2cbd4aad1dee92c5bd7a79d121dcff1e659c2a0c2174e/cint-1.0.0-py3-none-any.whl", hash = "sha256:8aa33028e04015711c0305f918cb278f1dc8c5c9997acdc45efad2c7cb1abf50", size = 5573, upload-time = "2019-03-19T01:07:46.496Z" },
+]
+
[[package]]
name = "click"
version = "8.3.1"
@@ -524,6 +600,62 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
]
+[[package]]
+name = "cryptography"
+version = "49.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
+ { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
+ { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
+ { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
+ { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" },
+ { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" },
+ { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" },
+ { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" },
+ { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" },
+ { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" },
+ { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
+ { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
+ { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
+ { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
+ { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" },
+ { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" },
+]
+
[[package]]
name = "cuda-bindings"
version = "12.9.4"
@@ -548,6 +680,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/02/4dbe7568a42e46582248942f54dc64ad094769532adbe21e525e4edf7bc4/cuda_pathfinder-1.3.3-py3-none-any.whl", hash = "sha256:9984b664e404f7c134954a771be8775dfd6180ea1e1aef4a5a37d4be05d9bbb1", size = 27154, upload-time = "2025-12-04T22:35:08.996Z" },
]
+[[package]]
+name = "cwsandbox"
+version = "0.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "grpcio" },
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ef/d9/540f20685c878f86f8588bff841618e1a125bfb13c8ba4071fe9e041477d/cwsandbox-0.26.0.tar.gz", hash = "sha256:541fbef6fd5cf7b70e2692bf3f35a2ae8f2a577e5caf29162dececb42e973616", size = 489383, upload-time = "2026-06-11T13:09:00.329Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/61/ac962e3c59d3c349046d204a67c5f301b10ebab4c413b079862fd234a7d5/cwsandbox-0.26.0-py3-none-any.whl", hash = "sha256:88143a5959a93f1a0145a7897d57caabe9c488051e287f185da1a83451896105", size = 178221, upload-time = "2026-06-11T13:08:58.751Z" },
+]
+
+[package.optional-dependencies]
+cli = [
+ { name = "click" },
+]
+
[[package]]
name = "datasets"
version = "4.5.0"
@@ -591,6 +742,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" },
]
+[[package]]
+name = "diskcache-weave"
+version = "5.6.3.post1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/52/634e1f43486489fdaded1a7c9bd3524b7e0ca9bcc43af426afa511c541e2/diskcache_weave-5.6.3.post1.tar.gz", hash = "sha256:1fe7e648d1d85d517c05b296f1692e7c425a71714dc31a4b7a584a8f8f5604f2", size = 68297, upload-time = "2026-03-19T14:57:54.299Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/8d/92887441bc338fb8d0b8ea75eb0392c00e20a85ec0bbe02f273188849568/diskcache_weave-5.6.3.post1-py3-none-any.whl", hash = "sha256:b00e9842b74eeecf314456f9c833a6d4f7792ed12b20297b4d3b9df7859ee66f", size = 45905, upload-time = "2026-03-19T14:57:52.819Z" },
+]
+
[[package]]
name = "distlib"
version = "0.4.0"
@@ -600,6 +760,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
]
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
[[package]]
name = "docstring-to-markdown"
version = "0.17"
@@ -662,6 +831,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
]
+[[package]]
+name = "fickling"
+version = "0.1.12"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/20/d3c2bdb9235b777763a4afc7cc3673afcd162a707ec0988ae7141a540802/fickling-0.1.12.tar.gz", hash = "sha256:83f6ccc948e21edb9ebd92795069536b47f481ce6add62598eac608b31576821", size = 357026, upload-time = "2026-06-26T23:55:57.829Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/c0/c65003b71abc4ffddbae99ae86952a21ff859d81b0f1331f79239fe5a58e/fickling-0.1.12-py3-none-any.whl", hash = "sha256:6232b72857e6ee9d729922811b681132d49dc39abc896581390dad1c0eada814", size = 58960, upload-time = "2026-06-26T23:55:56.401Z" },
+]
+
[[package]]
name = "filelock"
version = "3.20.3"
@@ -791,27 +969,104 @@ http = [
]
[[package]]
-name = "gitdb"
-version = "4.0.12"
+name = "googleapis-common-protos"
+version = "1.75.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "smmap" },
+ { name = "protobuf" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" },
]
[[package]]
-name = "gitpython"
-version = "3.1.46"
+name = "gql"
+version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "gitdb" },
+ { name = "anyio" },
+ { name = "backoff" },
+ { name = "graphql-core" },
+ { name = "yarl" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/06/9f/cf224a88ed71eb223b7aa0b9ff0aa10d7ecc9a4acdca2279eb046c26d5dc/gql-4.0.0.tar.gz", hash = "sha256:f22980844eb6a7c0266ffc70f111b9c7e7c7c13da38c3b439afc7eab3d7c9c8e", size = 215644, upload-time = "2025-08-17T14:32:35.397Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/94/30bbd09e8d45339fa77a48f5778d74d47e9242c11b3cd1093b3d994770a5/gql-4.0.0-py3-none-any.whl", hash = "sha256:f3beed7c531218eb24d97cb7df031b4a84fdb462f4a2beb86e2633d395937479", size = 89900, upload-time = "2025-08-17T14:32:34.029Z" },
+]
+
+[package.optional-dependencies]
+httpx = [
+ { name = "httpx" },
+]
+
+[[package]]
+name = "graphql-core"
+version = "3.2.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4d/90/f2aff026ab4aebd80eb71905106a0885f4cfde85dcf965543f45bed0d9ee/graphql_core-3.2.11.tar.gz", hash = "sha256:e7e156d10beb127cab5c89ff0da71416fc73d27c484a4757d3b2d35633774802", size = 528407, upload-time = "2026-06-05T13:45:22.915Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/00/15/b92b4e1d88d02c6eff9733c9eea21846ab435cc4d813d84ccc5d335955df/graphql_core-3.2.11-py3-none-any.whl", hash = "sha256:0b3e35ff41e9adba53021ab0cef475eb18f57c7f53f0f2ca55567fbf3c537ea0", size = 214879, upload-time = "2026-06-05T13:45:21.245Z" },
+]
+
+[[package]]
+name = "graphviz"
+version = "0.21"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" },
+]
+
+[[package]]
+name = "grpcio"
+version = "1.82.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/26/5b/e5092af97fa671ca279b3e373251af4bf87d5fbda7dc85f6a616899562a7/grpcio-1.82.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:0ddb18a9a9e1f46692b3567ae4abb3f8d117ce6afea48650f8eca06d8ab5d06f", size = 6181472, upload-time = "2026-07-08T12:34:31.009Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/8f/18053a3a2ca03d0c2a1b8cc7271e705007a16aa5dae84bac00935c5b1a7f/grpcio-1.82.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cf855b1af246720f567b0ce5d0724d45dfa4188eecc3296a2a69257b11b9e94b", size = 11970995, upload-time = "2026-07-08T12:34:33.603Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/7e/21b1acb052876ad00959ec4d1b05fe08607d650bcfa282073bb164c2703c/grpcio-1.82.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb30cb13e25bc13cea70ffc69d6d90c49d36ea6c1d4549e6912f70177834cac", size = 6760127, upload-time = "2026-07-08T12:34:36.122Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/12/25eef9c245c54f0061317d13a302357fe8ea03bac240b2b02ececcf54da4/grpcio-1.82.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1e822b2774f719c017cbe700b6e47173b6ae290fb84906f52a5a3c2c60b62e1e", size = 7484377, upload-time = "2026-07-08T12:34:38.368Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/41/1a348767eb9d9bd7765dc4fa8a01723d3bb386d67f981ee5c6f9c02b8b1c/grpcio-1.82.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5dafb1ece8ed45dee7c738f166ec82e19673221ed5ab8967f72858a4685345b2", size = 6924269, upload-time = "2026-07-08T12:34:40.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/b9/3aae7a03d34c86ea27988db859a6087c186f6c3f53f9b551e07afd989bfa/grpcio-1.82.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06503106e7271e0a49fd5a1ac04747f1e47e87d900476db6fe45bc87ee411f4", size = 7531848, upload-time = "2026-07-08T12:34:43.277Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2e/3c4afa625d0dac9090707966916284c035fc5b2fb3e2c51e156accee6735/grpcio-1.82.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff99bc8cafb6a952201c37b995f425e641c93ffa6e072258525feab57290141d", size = 8568217, upload-time = "2026-07-08T12:34:45.502Z" },
+ { url = "https://files.pythonhosted.org/packages/20/9c/d8489c628e73e20a3d034e7f66912de7b1acb405f01d388f056a88e47924/grpcio-1.82.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:644ae1b94266ac785330f4590a69e52b6a7eb73029043a02209db81c81397d69", size = 7938771, upload-time = "2026-07-08T12:34:48.323Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/b7/0a92cfd1658f3a896d4aa12d4efeb7dd4ddfc723725ae22741a5241ea710/grpcio-1.82.1-cp311-cp311-win32.whl", hash = "sha256:e203d2e19d471630084a16c815616f8211dff21c268ab3c5f5bf38417832e074", size = 4256432, upload-time = "2026-07-08T12:34:50.432Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/6a/2872c761b025d9ec74386f22a4a7d59c5a5b00ebf718761b33739ffc45de/grpcio-1.82.1-cp311-cp311-win_amd64.whl", hash = "sha256:0d8299c285fe6cc6a1f56badf8d3bc5078c8d20273ee64bafa3783b4bc29a769", size = 5009633, upload-time = "2026-07-08T12:34:52.67Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/88/d1350bf3343a2ed87d801584e40609f6c6bd3087926eeca03de50348cf4a/grpcio-1.82.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:c09bd5fa0d5b1fbd773ec349fe61441c3e4ebf168c229aa7538a820bdfad6a58", size = 6144689, upload-time = "2026-07-08T12:34:55.567Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/33/71875cdecd27c24ac1385d4783a09853f01b84a825a36aec2a2bc7d0d080/grpcio-1.82.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1eae24810720734598e3e6a1a528d5de0f265fe3fc86575e9ecce424b9ec7379", size = 11952034, upload-time = "2026-07-08T12:34:58.128Z" },
+ { url = "https://files.pythonhosted.org/packages/82/b2/d9125df3d8a140dec12cc82c05b7deafedeababcff6496f28b2fd5634d10/grpcio-1.82.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6bd5daf5bde7b24d7ad2cbaf8bf9eac620d96222016bb5e7ddde930dec0673f", size = 6710772, upload-time = "2026-07-08T12:35:01.33Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9b/69e2d1627398b964f34437dc476a5aff5a2cc8e7f247d26272b5674b5faf/grpcio-1.82.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecfde669cb687ac020d31ff76debe5dc7a62213335f02262eb6625628da1c03", size = 7450677, upload-time = "2026-07-08T12:35:03.926Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/e7/8f855ca29c294956122a2a73023655b9b02602d5111dad2b9b00e7631c68/grpcio-1.82.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:011c8badee95734dee8bf05ce3464756a0ac3ebb8d443afd20c0e2b5e4640ad9", size = 6886855, upload-time = "2026-07-08T12:35:06.174Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/f0/fa87e85f49925f44c479d07e58b051e69bcfef6b6d5fbc6749d140f6730a/grpcio-1.82.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b85f4564926fb23114d239392bdcae200db1e6179629edd7d7ab0ab89c96a197", size = 7501323, upload-time = "2026-07-08T12:35:08.49Z" },
+ { url = "https://files.pythonhosted.org/packages/67/55/2e0b10ae1d3ef9dcc480b91dc2158f4931fc4675d3af0a2836e39b2a744f/grpcio-1.82.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c0c8270833395644c3fe6b6a806397955a2bc0538000a19a78b90c05a6c16e0", size = 8536899, upload-time = "2026-07-08T12:35:10.975Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/95/a3d8b0431fa221efc51ee39d73595ede74ba82a43b7c4313192e580face2/grpcio-1.82.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ba199205ff46c7778290fe1673c91ac8e7e45678dd5c86e9e56fa33ec8788f6", size = 7913892, upload-time = "2026-07-08T12:35:13.944Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/92/f2651ec704d9852a56faef394775038afba435b50ce82ab2404d119c3355/grpcio-1.82.1-cp312-cp312-win32.whl", hash = "sha256:06127691866e295c14e84a1fb86356dd962254f6abd0da4ca4b001eea9e89438", size = 4240985, upload-time = "2026-07-08T12:35:16.048Z" },
+ { url = "https://files.pythonhosted.org/packages/96/4f/a5fe8bf0d0a1b24855f370293075c931f27de4eb55f0f158786095bf3c11/grpcio-1.82.1-cp312-cp312-win_amd64.whl", hash = "sha256:1fa3223a3a2e1db74f4c2b255189eb7ea875dfba56e221d252ee3fc7b204778e", size = 5001580, upload-time = "2026-07-08T12:35:18.689Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/3e/496992d08c0aaa11272eb6228dc8ab947da01fe835de243cd00521bce4c4/grpcio-1.82.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b454a2d97bfab7565683a02345f86bd182ab69fd7c2bdb7414171e7538f266b1", size = 6146068, upload-time = "2026-07-08T12:35:21.365Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/8f/f263d6f14fdba6b56cfadd91fd3e158a52682b72c6016d1f8723d435659f/grpcio-1.82.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3dde70abfc80b3be11de53ba0d601c439e7fb2afd3583ad1788d1146bec92fdc", size = 11948600, upload-time = "2026-07-08T12:35:24.312Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/14/3a02e6ee49c2d85bc15eaae321e0e11ab3542cad3c5b2de121ecce0c4296/grpcio-1.82.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5523099c98c292ea1ae08e617249db760c56a78f8deae879027fe7d1ffbcbf6", size = 6714591, upload-time = "2026-07-08T12:35:27.027Z" },
+ { url = "https://files.pythonhosted.org/packages/69/80/58e3738696f48ab7645347b98d8a7f93d10e00e6218388fbfcd6c9310e3d/grpcio-1.82.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5e5c4dc0a59b0f8490a6bdfd6fc8395b9d8ad8a8407c7d67ca7b5bba15c0877f", size = 7454995, upload-time = "2026-07-08T12:35:29.599Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6c/2557c1a889363072fbf2285ecd0e8c44860d4dbd60f017a32537c5b863e2/grpcio-1.82.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c40d94ba820329cc191981bc22fa6f6eed0799c6d921f3c6709521d59d4a2fd7", size = 6888621, upload-time = "2026-07-08T12:35:32.38Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/66/907706ccaff1223f1e10fd5b37fc16faead43392fccb4e786e7e390ac141/grpcio-1.82.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c816180e31e273caaec6f8bd86a8392499d5bbb26f41da44e3dce48bde69095", size = 7505069, upload-time = "2026-07-08T12:35:35.072Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/7c/ff97b0d0f635987ee5ec80dfedafa1aad629303745d48e8637d10eec5b80/grpcio-1.82.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31fd780b261830720cb70b0fd8f0aa51d49e75a66d7464ad2e31d4b765f2580", size = 8535384, upload-time = "2026-07-08T12:35:37.954Z" },
+ { url = "https://files.pythonhosted.org/packages/62/9e/a97fddd970a8d1588cade06eca20443761c1858b0ad6590a5c835aa18062/grpcio-1.82.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d76152d7c31d7210d4a106e5d8b64da5bba5d6abf11be30e2f7b0a0c59bbcbf", size = 7910707, upload-time = "2026-07-08T12:35:40.797Z" },
+ { url = "https://files.pythonhosted.org/packages/20/e4/eaba1517888af483a88d449eb7566f0f7f63446d46f339c5891798435875/grpcio-1.82.1-cp313-cp313-win32.whl", hash = "sha256:38e9dcb5258226fb3282630b31b16a968df52c8c6ad514af540646e0a4578f8a", size = 4240363, upload-time = "2026-07-08T12:35:43.298Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/42/66a98d47732e35290bef722f6149fed3709cd4cf61166f6f53a12f417302/grpcio-1.82.1-cp313-cp313-win_amd64.whl", hash = "sha256:3dbfb52c36d9511ac2b8e6c94fdde837b393ae520cc321f52a333a2deedf5a90", size = 5000980, upload-time = "2026-07-08T12:35:46.262Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/cb/cf9ae9e164c6e6dc8a494faa9771763df9da150eefe19671009624d1559f/grpcio-1.82.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:35f990f7784c8fd2872644f07f96ebb4d9e48e145a190ab80d0280af91a1bfb2", size = 6146901, upload-time = "2026-07-08T12:35:49.261Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/2a/eccf26dbcfb7f7cab8027c5490a16c8937c5aa7a2ec20a3eab2cf7a43165/grpcio-1.82.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:46536a4a1f4434df3c851b9254ff6fc7df5705b273681a15ca277d5921c178a0", size = 11954756, upload-time = "2026-07-08T12:35:52.196Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/75/3b3b4a3cc9f084b026af96e1d3e539b1af29ec7f41ed0dfff3cb99cc8626/grpcio-1.82.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d6650a7c1ebb7921c70e12a385439a8118efb99e669fa9ed31cf25db1843937c", size = 6723087, upload-time = "2026-07-08T12:35:54.973Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/8b/b0f0c9b1400a99a4da4c09b114f101b192f8f11192e76f620b8962f5d90b/grpcio-1.82.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8e110c66df5204c0506d6c8787b35d48b8b699ef5aa366d6c4d67325c67fe9a", size = 7454542, upload-time = "2026-07-08T12:35:57.586Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/bd/428e38868382aa193697a5aa53973f29c58e58ba4268aa0c86a2715ee58b/grpcio-1.82.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f853eae07235a51a27bb5d6a9a175a59ca55dc9b99edc6ce2f76f07332d333ae", size = 6889588, upload-time = "2026-07-08T12:36:00.012Z" },
+ { url = "https://files.pythonhosted.org/packages/49/ce/03e01d5e10259bf5c08ee50570cc94724e79c956f61fd2f09b341af0956c/grpcio-1.82.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60b0f2c95337694fc094b77d9f60f50566c84b5677393e342eb98daeee242d98", size = 7514166, upload-time = "2026-07-08T12:36:02.693Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/59/278b4b600329e2ba3849f3c1ea3c820b3a01b38a7ad184ba09595e8d2733/grpcio-1.82.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b064fc444812bdaa9825d33c26f8d732d63ee6a5d78557c1faf92c98687fed27", size = 8536166, upload-time = "2026-07-08T12:36:05.349Z" },
+ { url = "https://files.pythonhosted.org/packages/44/27/7ccf2ef00f27a8e47a79d641c8ceaf7d3028c7a03d9a97b4c8a9a783c086/grpcio-1.82.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d7ede11d747b4e1bd05e3bc0260e155b65a88735a895a10f6521f19b889511e", size = 7912572, upload-time = "2026-07-08T12:36:08.393Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/be/33742482d2753f2d3a1b7641664b6622262d44f2f3b609f13425dd86d36f/grpcio-1.82.1-cp314-cp314-win32.whl", hash = "sha256:3d21f19838dc255ecbb79321b15ae9b98fbddff4c3d4aedb0a81bdd7f4ab572a", size = 4321856, upload-time = "2026-07-08T12:36:10.899Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/67/03329c847172c78ddeb1eb9be6b444fdbc12775a84c958b27e427e7b926d/grpcio-1.82.1-cp314-cp314-win_amd64.whl", hash = "sha256:e20f1edbb15f99e3128ec86433f9785fd5a451d8f115e74fe0056134f092a9d5", size = 5141114, upload-time = "2026-07-08T12:36:13.595Z" },
]
[[package]]
@@ -931,6 +1186,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
]
+[[package]]
+name = "intervaltree"
+version = "3.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "sortedcontainers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/53/c3/b2afa612aa0373f3e6bb190e6de35f293b307d1537f109e3e25dbfcdf212/intervaltree-3.2.1.tar.gz", hash = "sha256:f3f7e8baeb7dd75b9f7a6d33cf3ec10025984a8e66e3016d537e52130c73cfe2", size = 1231531, upload-time = "2025-12-24T04:25:06.773Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/7f/8a80a1c7c2ed05822b5a2b312d2995f30c533641f8198366ba2e26a7bb03/intervaltree-3.2.1-py2.py3-none-any.whl", hash = "sha256:a8a8381bbd35d48ceebee932c77ffc988492d22fb1d27d0ba1d74a7694eb8f0b", size = 25929, upload-time = "2025-12-24T04:25:05.298Z" },
+]
+
[[package]]
name = "ipython"
version = "9.10.0"
@@ -1014,6 +1281,92 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
+[[package]]
+name = "jiter"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4e/3f/fae6cc967d120ec89e31c5418a51176d8278b3087fbb384a9176754f353c/jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3", size = 309289, upload-time = "2026-06-29T13:02:52.301Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/e3/97c6c3562c077f6247d6e6ce5c82562500b6316c0d928e97e106b7a1321a/jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e", size = 315181, upload-time = "2026-06-29T13:02:53.964Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/89/d8d073f8aa2667e46c6c0873f86fe4a512bba4293cc730f626a076211a62/jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037", size = 340939, upload-time = "2026-06-29T13:02:55.412Z" },
+ { url = "https://files.pythonhosted.org/packages/87/c9/db4fda3ed73fb864139305e935e5b8b38a5a24692a5a9dd356c22f1b9c8d/jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e", size = 364932, upload-time = "2026-06-29T13:02:57.28Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/74/52b5e86241057f52ddd7c9a580f90effb51f9d06239f6fc612279b91a838/jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b", size = 461132, upload-time = "2026-06-29T13:02:58.994Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/87/544a700f7447c1f31c5d7833821a4daa5683165c2d5a094fbf5b5800c3dc/jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c", size = 374857, upload-time = "2026-06-29T13:03:00.455Z" },
+ { url = "https://files.pythonhosted.org/packages/40/cd/0fcc3f7d39183674d5bfa9ec640faaeb506c60be7c8f94625dfba366e37c/jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee", size = 347053, upload-time = "2026-06-29T13:03:02.045Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ae/c7e64e7932ad597fa395b61440b249ada6366716e25c6e08dd2afbd021e6/jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03", size = 356153, upload-time = "2026-06-29T13:03:03.706Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/1c/1c719044f14da814e1a060191ab19b96f3e99207bc5b4bfc6d6be34b3f80/jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea", size = 393956, upload-time = "2026-06-29T13:03:05.165Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/dc/7b2f303a2847207e265503853a2d964a55354cffd62a5f2936c155486798/jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91", size = 521081, upload-time = "2026-06-29T13:03:06.886Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/5f/501cf6e1e09caeb420195179ffc6f62aca603f1220ec53fd80d0d70b3e56/jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3", size = 552085, upload-time = "2026-06-29T13:03:08.339Z" },
+ { url = "https://files.pythonhosted.org/packages/79/54/aa5be86520113b79455c3877f3d1f07a348098df4083ba3688e9537e52dd/jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7", size = 206755, upload-time = "2026-06-29T13:03:09.653Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ec/2feb893eb330bd69b413866f4d5daada33c3962f1c6f270c91ca2d87fdf9/jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1", size = 199155, upload-time = "2026-06-29T13:03:10.979Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/9c/ca040d94415048a3666fc237774df8151c96f8d2b661cbe3b184acc95876/jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056", size = 194403, upload-time = "2026-06-29T13:03:12.341Z" },
+ { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" },
+ { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" },
+ { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/8c/f5ef7b65f0df47afa16596969defb281ebb86e96df346d62be6fd853d620/jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e", size = 362573, upload-time = "2026-06-29T13:03:18.781Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0b/ace4354da061ee38844a0c27dc2c21eecd27aea119e8da324bea987522d0/jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290", size = 457979, upload-time = "2026-06-29T13:03:20.293Z" },
+ { url = "https://files.pythonhosted.org/packages/55/40/c0253d3772eb9dcd8e6606ee9b2d53ec8e5b814589c47f140aa585f21eaa/jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01", size = 372302, upload-time = "2026-06-29T13:03:21.739Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/d2/4839422241aa12860ce597b20068727094ba0bc480723c74924ca5bad483/jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48", size = 343805, upload-time = "2026-06-29T13:03:23.384Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/59/e196888a05befdda7dbe299b722d56f2f6eec65402bc34c0a3306d595feb/jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9", size = 351107, upload-time = "2026-06-29T13:03:24.815Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/74/4cd9e0fca65232136400354b630fbfcd2de634e22ccbb96567725981b548/jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5", size = 388441, upload-time = "2026-06-29T13:03:26.266Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/8c/554691e48bc711299c0a293dd8a6179e24b2d66a54dc295421fcf64569c0/jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730", size = 516354, upload-time = "2026-06-29T13:03:28.02Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/cb/01e9d69dc2cc6759d4f91e230b34489c4fdb2518992650633f9e20bece89/jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f", size = 547880, upload-time = "2026-06-29T13:03:29.534Z" },
+ { url = "https://files.pythonhosted.org/packages/79/70/2953195f1c6ad00f49fa67e13df7e60acb3dd4f387101bc15abccddd905e/jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274", size = 203473, upload-time = "2026-06-29T13:03:31.025Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/05/2909a8b10699a4d560f8c502b6b2c5f3991b682b1922c1eedda242b225bd/jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7", size = 196905, upload-time = "2026-06-29T13:03:32.472Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/a9/6b82bb1c8d7790d602489b967b982a909e5d092875a6c2ade96444c8dfc5/jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331", size = 190618, upload-time = "2026-06-29T13:03:34.672Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" },
+ { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" },
+ { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" },
+ { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" },
+ { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" },
+ { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" },
+ { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" },
+ { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" },
+ { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" },
+ { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" },
+ { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" },
+ { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" },
+ { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" },
+ { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" },
+ { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" },
+ { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" },
+ { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" },
+ { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" },
+ { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" },
+ { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" },
+ { url = "https://files.pythonhosted.org/packages/06/d3/8e278946d43eeca2585b4dd0834a887cd71136329b837f3a16ed86a8b4b0/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad", size = 304518, upload-time = "2026-06-29T13:05:00.172Z" },
+ { url = "https://files.pythonhosted.org/packages/72/43/28d4ef495028bf0506a413d4db3f4eb3e7288a382e0f065f306a17bbeb5e/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f", size = 310207, upload-time = "2026-06-29T13:05:02.123Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ca/c366b1012da1d640de975d9683acd44e4d150d9068845d0ca2610435253f/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5", size = 342771, upload-time = "2026-06-29T13:05:03.55Z" },
+ { url = "https://files.pythonhosted.org/packages/16/52/50cc4056fc1ae02e7154704e7ecc89df0afb8300222cfe8a52d3f67e4730/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85", size = 346468, upload-time = "2026-06-29T13:05:05.452Z" },
+ { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" },
+ { url = "https://files.pythonhosted.org/packages/78/f7/18a1afcd64f35314b68c1f23afcd9994d0bc13e65cc77517afff4e83986d/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2", size = 343885, upload-time = "2026-06-29T13:05:12.087Z" },
+]
+
[[package]]
name = "jmespath"
version = "1.1.0"
@@ -1059,6 +1412,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" },
]
+[[package]]
+name = "kaitaistruct"
+version = "0.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" },
+]
+
[[package]]
name = "kubernetes"
version = "35.0.0"
@@ -1888,6 +2250,106 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" },
]
+[[package]]
+name = "openai"
+version = "2.46.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/af/ac/f725c4efbda8657d02be684607e5a2e5ce362e4790fdbcbdfb7c15018647/openai-2.46.0.tar.gz", hash = "sha256:0421e0735ac41451cad894af4cddf0435bfbf8cbc538ac0e15b3c062f2ddc06a", size = 1114628, upload-time = "2026-07-17T02:48:06.05Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ea/7b/206238ebcb50b235942b1c66dba4974776f2057402a8d91c399be587d66a/openai-2.46.0-py3-none-any.whl", hash = "sha256:672381db55efb3a1e2610f29304c130cccdd0b319bace4d492b2443cb64c1e7c", size = 1637556, upload-time = "2026-07-17T02:48:03.695Z" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-common"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-proto" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/61/09/4d717852c1cf3f854b76c7110a5d00883bc3c99288b9b0dbcbeb9e306eb6/opentelemetry_exporter_otlp_proto_common-1.44.0.tar.gz", hash = "sha256:dc87a5a5bc58f149a56d1547e4691588fa12994cdc3bc039a694ccb3375862ac", size = 20202, upload-time = "2026-07-16T15:25:37.658Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5e/71/65fd9d54c10b860f87c045ccee1264cab7011268895d3528818a29c1172a/opentelemetry_exporter_otlp_proto_common-1.44.0-py3-none-any.whl", hash = "sha256:9a9fe61bba73d802904bc989f1d6b4a7b1ee40f06c40e98d6f85af65aaebb694", size = 17045, upload-time = "2026-07-16T15:25:18.201Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-http"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-common" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "requests" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1a/87/95e2a5aaa795b4e2260d74e16df2d5541deb2ea9de010bcd615f4dee2654/opentelemetry_exporter_otlp_proto_http-1.44.0.tar.gz", hash = "sha256:c633d7270ad6b57cd4cfbe8b0007a9e2e7c0cb50bd6c50fe2a7b245f721a09d8", size = 25806, upload-time = "2026-07-16T15:25:39.162Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/d0/fdeb1a98d8d3a6205f5f297c51b4a9bfe65126ab60339669bbe3dd54c2e2/opentelemetry_exporter_otlp_proto_http-1.44.0-py3-none-any.whl", hash = "sha256:838592fce774c1c8bb7b9a0a7facbfa82e17be5a8a4e94cef10cb84ae026bae3", size = 21850, upload-time = "2026-07-16T15:25:20.006Z" },
+]
+
+[[package]]
+name = "opentelemetry-proto"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/64/01/40ac4ae9a149263cc52c2cee200ddd80cb6d8db1a4610abf8eabce0fe771/opentelemetry_proto-1.44.0.tar.gz", hash = "sha256:c547a79c2f8c0c515d31509154682e5921c7cfd5ca67b70e1f9266e2c3e103f3", size = 46488, upload-time = "2026-07-16T15:25:45.34Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/7c/8be563d68e93bbefa5c8affb82ddcff91b3ad858ce49957ba7b16fd3e0ab/opentelemetry_proto-1.44.0-py3-none-any.whl", hash = "sha256:898b155a0e1557afd867478fb6158e8122a46329ca0bb8dc53cc55e98f017f56", size = 72483, upload-time = "2026-07-16T15:25:28.429Z" },
+]
+
+[[package]]
+name = "opentelemetry-sdk"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5d/77/a6592cbc7c8d9bcc9d6757a9df45e04a7c585e3e6e7a13456da522b21109/opentelemetry_sdk-1.44.0.tar.gz", hash = "sha256:cebe7f65dc12f26ead75c6064de12fd2a9052e5060c0272d402cfa203aae123b", size = 208624, upload-time = "2026-07-16T15:25:46.078Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/23/ff077e61886ee020a17ce9c8b6fa11c601c8d8345b09ea24f605445df62a/opentelemetry_sdk-1.44.0-py3-none-any.whl", hash = "sha256:df081c4c6bcfdb1211e3e86140376792643128a25f8d72d1d27675936e7e96ad", size = 137221, upload-time = "2026-07-16T15:25:29.534Z" },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.65b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8f/73/0cbdebcb4cf545fdd328da14f5137e37d0770c3f26185e478b0d15d94f50/opentelemetry_semantic_conventions-0.65b0.tar.gz", hash = "sha256:f9b2b81e9d5b64f11bc952075e7e9c7fb0aab075c7fd1c46d597f1b919852d60", size = 148774, upload-time = "2026-07-16T15:25:46.902Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a6/0e/49df70d9b81fb5cbae4bbf2a49d865b09bcbcbc4eb53f5851b1027738d78/opentelemetry_semantic_conventions-0.65b0-py3-none-any.whl", hash = "sha256:1cacde7b0ad306f84c5ef08c3dbe1bbaf20165bba6f8bff43b670e555a086bcb", size = 204645, upload-time = "2026-07-16T15:25:30.688Z" },
+]
+
[[package]]
name = "packaging"
version = "26.0"
@@ -1975,6 +2437,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
]
+[[package]]
+name = "pdfminer-six"
+version = "20260107"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "charset-normalizer" },
+ { name = "cryptography" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" },
+]
+
[[package]]
name = "pexpect"
version = "4.9.0"
@@ -1987,6 +2462,91 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
]
+[[package]]
+name = "pillow"
+version = "12.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" },
+ { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" },
+ { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" },
+ { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" },
+ { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" },
+ { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" },
+ { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" },
+ { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" },
+ { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" },
+ { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" },
+ { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" },
+ { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" },
+ { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" },
+ { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" },
+ { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" },
+ { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" },
+ { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" },
+ { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" },
+ { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" },
+ { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" },
+ { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" },
+ { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" },
+ { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" },
+ { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" },
+ { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" },
+ { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" },
+ { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" },
+ { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" },
+ { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" },
+ { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" },
+ { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" },
+]
+
[[package]]
name = "platformdirs"
version = "4.5.1"
@@ -2005,6 +2565,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
+[[package]]
+name = "polyfile-weave"
+version = "0.5.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "abnf" },
+ { name = "chardet" },
+ { name = "cint" },
+ { name = "fickling" },
+ { name = "filelock" },
+ { name = "graphviz" },
+ { name = "intervaltree" },
+ { name = "jinja2" },
+ { name = "kaitaistruct" },
+ { name = "networkx" },
+ { name = "pdfminer-six" },
+ { name = "pillow" },
+ { name = "pyreadline3", marker = "sys_platform == 'win32'" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/55/e5400762e3884f743d59291e71eaaa9c52dd7e144b75a11911e74ec1bac9/polyfile_weave-0.5.9.tar.gz", hash = "sha256:12341fab03e06ede1bfebbd3627dd24015fde5353ea74ece2da186321b818bdb", size = 6024974, upload-time = "2026-01-22T22:08:48.081Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/52/94/215005530a48c5f7d4ec4a31acdb5828f2bfb985cc6e577b0eaa5882c0e2/polyfile_weave-0.5.9-py3-none-any.whl", hash = "sha256:6ae4b1b5eeac9f5bfc862474484d6d3e33655fab31749d93af0b0a91fddabfc7", size = 1700174, upload-time = "2026-01-22T22:08:46.346Z" },
+]
+
[[package]]
name = "pre-commit"
version = "4.5.1"
@@ -2415,6 +3000,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" },
]
+[[package]]
+name = "pyreadline3"
+version = "3.5.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b6/6d/f94028646d7bbe6d9d873c47ee7c246f2d29129d253f0d96cb6fcab70733/pyreadline3-3.5.6.tar.gz", hash = "sha256:61e53218b99656091ddb077df9e71f25850e72e030b6183b39c9b7e6e4f4a9bf", size = 100368, upload-time = "2026-05-14T17:55:04.471Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl", hash = "sha256:8449b734232e42a5dcd74048e39b60db2839a4c38cf3ae2bf7707d58b5389c0d", size = 85243, upload-time = "2026-05-14T17:55:03.262Z" },
+]
+
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -2661,13 +3255,15 @@ dependencies = [
{ name = "marimo" },
{ name = "moutils", version = "0.3.12", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
{ name = "moutils", version = "0.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "openai" },
{ name = "ray" },
{ name = "ruamel-yaml" },
{ name = "statistics" },
{ name = "torch" },
{ name = "transformers" },
{ name = "typing-extensions" },
- { name = "wandb" },
+ { name = "wandb", extra = ["sandbox"] },
+ { name = "weave" },
]
[package.dev-dependencies]
@@ -2691,13 +3287,15 @@ requires-dist = [
{ name = "kubernetes", specifier = ">=35.0.0" },
{ name = "marimo", specifier = ">=0.23.6" },
{ name = "moutils", specifier = ">=0.3.12" },
+ { name = "openai", specifier = ">=2.46.0" },
{ name = "ray", specifier = ">=2.53.0" },
{ name = "ruamel-yaml", specifier = ">=0.19.1" },
{ name = "statistics", specifier = ">=1.0.3.5" },
{ name = "torch", specifier = ">=2.10.0" },
{ name = "transformers", specifier = ">=5.0.0" },
{ name = "typing-extensions", specifier = ">=4.15.0" },
- { name = "wandb", specifier = ">=0.24.2" },
+ { name = "wandb", extras = ["sandbox"], specifier = ">=0.28.1" },
+ { name = "weave", specifier = ">=0.53.2" },
]
[package.metadata.requires-dev]
@@ -3073,12 +3671,21 @@ wheels = [
]
[[package]]
-name = "smmap"
-version = "5.0.2"
+name = "sniffio"
+version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
]
[[package]]
@@ -3129,6 +3736,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
]
+[[package]]
+name = "tenacity"
+version = "9.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
+]
+
[[package]]
name = "tokenizers"
version = "0.22.2"
@@ -3453,11 +4069,10 @@ wheels = [
[[package]]
name = "wandb"
-version = "0.24.2"
+version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
- { name = "gitpython" },
{ name = "packaging" },
{ name = "platformdirs" },
{ name = "protobuf" },
@@ -3467,17 +4082,22 @@ dependencies = [
{ name = "sentry-sdk" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/97/5c/53cf9f74b89e90facc8c7892d1449f7b39527e50e5cd577346baeb97e423/wandb-0.24.2.tar.gz", hash = "sha256:968b5b91d0a164dfb2f8c604cdf69e6fb09de6596b85b9f9d3c916b71ae86198", size = 44237317, upload-time = "2026-02-05T00:12:16.739Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/92/fb/8d3f96a8b143060d6fa145462d0785981373e04694e4152555ccb5d23939/wandb-0.28.1.tar.gz", hash = "sha256:870ccb1a01238b0ac07c6fd96a0810a1f79090aba04ea29f4ee012ac8327705d", size = 40578119, upload-time = "2026-07-16T18:47:05.413Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/98/82/5299fa22faf2dd55f33f05c26bf908b11ea4d25f32ac270d4bf838b0d97e/wandb-0.24.2-py3-none-macosx_12_0_arm64.whl", hash = "sha256:755b8a92edd28e15c052dc2bdc4652e26bce379fa7745360249cbfc589ff5f53", size = 21640026, upload-time = "2026-02-05T00:11:55.267Z" },
- { url = "https://files.pythonhosted.org/packages/ca/38/33cb321258778c25c00fb7eb578e69ce99428a66d4376eee4058f230a21a/wandb-0.24.2-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:5e6c0ad176792c7c3d1620a2ad65bd9a5f3886c69362af540d3667bfc97b67fb", size = 22894053, upload-time = "2026-02-05T00:11:58.304Z" },
- { url = "https://files.pythonhosted.org/packages/3e/99/33b0281ac9a0b0c251195e6ce6cb310efa2f84ee117a15e9997fc2f9503b/wandb-0.24.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:85861f9b3e54a07b84bade0aa5f4caa156028ab959351d98816a45e3b1411d35", size = 21286409, upload-time = "2026-02-05T00:12:00.584Z" },
- { url = "https://files.pythonhosted.org/packages/70/c8/1b758bd903afee000f023cd03f335ff328a21b3914f9f9deda49b1e57723/wandb-0.24.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:38661c666e70d7e1f460fc0a0edab8a393eaaa5f8773c17be534961a7022779d", size = 23026085, upload-time = "2026-02-05T00:12:02.682Z" },
- { url = "https://files.pythonhosted.org/packages/60/87/724583f258aaeb2c368c79d7412167ce628f8a5ca667faed3cd427dd3be2/wandb-0.24.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:656a4272000999569eb8e0773f1259403bc6bd3e7d1c7d2238d3e359874da9c4", size = 21342088, upload-time = "2026-02-05T00:12:05.375Z" },
- { url = "https://files.pythonhosted.org/packages/1e/5c/e9b36ddc9beb2745a4fb1ec67ae7f995c31f7305a6d17837b72b228360ff/wandb-0.24.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:33cba098d95fd46720cc9023bd23e4a38e9b11836a836b4a57b8d41cff8985f2", size = 23120819, upload-time = "2026-02-05T00:12:07.487Z" },
- { url = "https://files.pythonhosted.org/packages/f6/6e/1ad011da4a5c860fdb88645c738a2dae914b1eea2249aa606659ccd1443f/wandb-0.24.2-py3-none-win32.whl", hash = "sha256:70db8680e8d7edb5bd60dfb7f31aeb5af30b31ad72498c47e1aba7471c337bb2", size = 22295643, upload-time = "2026-02-05T00:12:09.85Z" },
- { url = "https://files.pythonhosted.org/packages/38/8b/721c77616bd1fca8963bffef309da09cdff71002f9d4201dfd5bd370591a/wandb-0.24.2-py3-none-win_amd64.whl", hash = "sha256:a78ac1fa116b196cd33250b3d80f4a5c05c141ad949175515c007ec9826e49a6", size = 22295646, upload-time = "2026-02-05T00:12:11.898Z" },
- { url = "https://files.pythonhosted.org/packages/3a/9a/f3919d7ee7ba99dabf0aac7e299c6c328f5eae94f9f6b28c76005f882d5d/wandb-0.24.2-py3-none-win_arm64.whl", hash = "sha256:b42614b99f8b9af69f88c15a84283a973c8cd5750e9c4752aa3ce21f13dbac9a", size = 20268261, upload-time = "2026-02-05T00:12:14.353Z" },
+ { url = "https://files.pythonhosted.org/packages/06/21/8df50164d07623cfcefec19bbf9327d9be84b637a827cea1f0c06db005fd/wandb-0.28.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:da909a76e65c64c0d93acc485d2a19f66e336f1e3f725f1c98a070883e084943", size = 24277925, upload-time = "2026-07-16T18:46:42.383Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/18/6c3da7e6cb215ad363324db8dc4d83b93626f5e339822b05b1c38a6097fd/wandb-0.28.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:3da3db219c54bfd1082c00e9061c8ea894ba43e42733b5af00bb10c09d7158fe", size = 25480852, upload-time = "2026-07-16T18:46:45.102Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/1a/d15bcfb4417fa69edcaa33db8ea012db733da1057e193b047e3f69fdd671/wandb-0.28.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ae9ae6fb29e2e2b1d097ed8b75c0c0240c778c2a8cad1d996dee870a1e401c2c", size = 24832138, upload-time = "2026-07-16T18:46:47.433Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/da/49924c7df2952dfd82c86c3779c339c0c3d6f6439387c03d97d0470c3658/wandb-0.28.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8cfb898b6a6c884d9c9294b02764e88bce65049f027a124d6bee53fe722469b6", size = 26486533, upload-time = "2026-07-16T18:46:49.839Z" },
+ { url = "https://files.pythonhosted.org/packages/11/c0/06b23518e29690784f1b3081e39c7679ca076cb0af094cb9b4bb309150f5/wandb-0.28.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cf2b1533945395e4fdbe6182b272bb0ca8a02c10b3086a395e2d57686ae3ed0d", size = 25022635, upload-time = "2026-07-16T18:46:52.376Z" },
+ { url = "https://files.pythonhosted.org/packages/23/30/6de2f7995a8a6eecbd03d24c79a139a734c0168f5520cf4c7ccb43c1dbbc/wandb-0.28.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7233061080507a4b4098bed1ccb381ce6f890c60397cd4153d060285bcb267bd", size = 27008895, upload-time = "2026-07-16T18:46:55.025Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/83/49deab9447687625371435ca21b6da82f223f1c7d014d77386b9cb91833c/wandb-0.28.1-py3-none-win32.whl", hash = "sha256:4bc461cda3ce23a19d8df5e42981a664d95fa3231efb10fd1e85d9d4824c7d29", size = 24418398, upload-time = "2026-07-16T18:46:57.43Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/6f/ed6616b11ea15b8ceabedcaa567286c1c9ec65fa50563230a90bfb627cc5/wandb-0.28.1-py3-none-win_amd64.whl", hash = "sha256:d98a10370162b1e970850237114c56e9c4c58f3cb701e4b8cb38f36f6749fd52", size = 24418404, upload-time = "2026-07-16T18:47:00.427Z" },
+ { url = "https://files.pythonhosted.org/packages/07/78/75b6827a6665337a715c5347c5edbd84eca660f7a0f48d8d6d24d1f66bee/wandb-0.28.1-py3-none-win_arm64.whl", hash = "sha256:4aa07f13dd3bcac2c0524c8d0f49f76e83ab5c1054fd09f3b1a436cfcde146a6", size = 22299006, upload-time = "2026-07-16T18:47:02.71Z" },
+]
+
+[package.optional-dependencies]
+sandbox = [
+ { name = "cwsandbox", extra = ["cli"] },
]
[[package]]
@@ -3489,6 +4109,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" },
]
+[[package]]
+name = "weave"
+version = "0.53.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cachetools" },
+ { name = "click" },
+ { name = "diskcache-weave" },
+ { name = "gql", extra = ["httpx"] },
+ { name = "jsonschema" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-http" },
+ { name = "opentelemetry-sdk" },
+ { name = "packaging" },
+ { name = "polyfile-weave" },
+ { name = "pydantic" },
+ { name = "sentry-sdk" },
+ { name = "tenacity" },
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/b1/566cd40fa3455f2601306a4ebc1ea6c3f26161a01cbdeda668da1479f4df/weave-0.53.2.tar.gz", hash = "sha256:2e14e3279ebc62eb529aabf498aa02d526a01f18a189dfcfec19d2f38e75832a", size = 1148019, upload-time = "2026-07-16T23:38:52.194Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/e9/a22ed23dae0f1231b66afb84bd9460fa9b7c82a81841b7eb0c83593de06f/weave-0.53.2-py3-none-any.whl", hash = "sha256:0e44fcd002ed6e77f759f69a6b257edea8b645612c4b2cb41a8291f697299a3f", size = 1390710, upload-time = "2026-07-16T23:38:50.058Z" },
+]
+
[[package]]
name = "websocket-client"
version = "1.9.0"