programasweights 0.4.6__tar.gz → 0.4.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. programasweights-0.4.7/.cursor/rules/releases.mdc +17 -0
  2. programasweights-0.4.7/.github/workflows/release.yml +72 -0
  3. {programasweights-0.4.6 → programasweights-0.4.7}/.github/workflows/test.yml +13 -1
  4. {programasweights-0.4.6 → programasweights-0.4.7}/AGENTS.md +36 -0
  5. {programasweights-0.4.6 → programasweights-0.4.7}/CHANGELOG.md +11 -0
  6. {programasweights-0.4.6 → programasweights-0.4.7}/PKG-INFO +15 -1
  7. {programasweights-0.4.6 → programasweights-0.4.7}/PYPI_README.md +14 -0
  8. {programasweights-0.4.6 → programasweights-0.4.7}/README.md +14 -0
  9. programasweights-0.4.7/RELEASING.md +82 -0
  10. {programasweights-0.4.6 → programasweights-0.4.7}/docs/api-reference/cli.md +7 -2
  11. {programasweights-0.4.6 → programasweights-0.4.7}/docs/api-reference/python-sdk.md +36 -5
  12. {programasweights-0.4.6 → programasweights-0.4.7}/docs/api-reference/rest-api.md +21 -1
  13. {programasweights-0.4.6 → programasweights-0.4.7}/docs/getting-started/first-program.md +11 -0
  14. {programasweights-0.4.6 → programasweights-0.4.7}/docs/getting-started/installation.md +3 -0
  15. {programasweights-0.4.6 → programasweights-0.4.7}/docs/index.md +14 -0
  16. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/__init__.py +64 -8
  17. programasweights-0.4.7/programasweights/_remote.py +99 -0
  18. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/cli.py +27 -10
  19. {programasweights-0.4.6 → programasweights-0.4.7}/pyproject.toml +1 -1
  20. programasweights-0.4.7/scripts/release_metadata.py +156 -0
  21. programasweights-0.4.7/tests/test_release_metadata.py +296 -0
  22. programasweights-0.4.7/tests/test_remote_inference.py +294 -0
  23. {programasweights-0.4.6 → programasweights-0.4.7}/.gitignore +0 -0
  24. {programasweights-0.4.6 → programasweights-0.4.7}/.readthedocs.yaml +0 -0
  25. {programasweights-0.4.6 → programasweights-0.4.7}/LICENSE +0 -0
  26. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/001-llama-cpp-over-pytorch.md +0 -0
  27. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/002-q4_0-adapter-format.md +0 -0
  28. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/003-single-spec-field.md +0 -0
  29. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/004-compiler-naming.md +0 -0
  30. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/005-vllm-hidden-states.md +0 -0
  31. {programasweights-0.4.6 → programasweights-0.4.7}/docs/adr/006-email-api-key-auth.md +0 -0
  32. {programasweights-0.4.6 → programasweights-0.4.7}/docs/advanced/adrs.md +0 -0
  33. {programasweights-0.4.6 → programasweights-0.4.7}/docs/advanced/architecture.md +0 -0
  34. {programasweights-0.4.6 → programasweights-0.4.7}/docs/architecture.md +0 -0
  35. {programasweights-0.4.6 → programasweights-0.4.7}/docs/case-studies/alien-taboo.md +0 -0
  36. {programasweights-0.4.6 → programasweights-0.4.7}/docs/case-studies/log-monitoring.md +0 -0
  37. {programasweights-0.4.6 → programasweights-0.4.7}/docs/case-studies/semantic-search.md +0 -0
  38. {programasweights-0.4.6 → programasweights-0.4.7}/docs/case-studies/site-navigation.md +0 -0
  39. {programasweights-0.4.6 → programasweights-0.4.7}/docs/case-studies/tool-calling.md +0 -0
  40. {programasweights-0.4.6 → programasweights-0.4.7}/docs/getting-started/naming-programs.md +0 -0
  41. {programasweights-0.4.6 → programasweights-0.4.7}/docs/guide/browser-inference.md +0 -0
  42. {programasweights-0.4.6 → programasweights-0.4.7}/docs/guide/how-it-works.md +0 -0
  43. {programasweights-0.4.6 → programasweights-0.4.7}/docs/guide/local-inference.md +0 -0
  44. {programasweights-0.4.6 → programasweights-0.4.7}/docs/guide/writing-good-specs.md +0 -0
  45. {programasweights-0.4.6 → programasweights-0.4.7}/docs/hub/browsing-programs.md +0 -0
  46. {programasweights-0.4.6 → programasweights-0.4.7}/docs/hub/feedback-cases.md +0 -0
  47. {programasweights-0.4.6 → programasweights-0.4.7}/docs/hub/publishing-programs.md +0 -0
  48. {programasweights-0.4.6 → programasweights-0.4.7}/docs/requirements.txt +0 -0
  49. {programasweights-0.4.6 → programasweights-0.4.7}/examples/flask_app.py +0 -0
  50. {programasweights-0.4.6 → programasweights-0.4.7}/examples/jupyter_notebook.py +0 -0
  51. {programasweights-0.4.6 → programasweights-0.4.7}/examples/langchain_integration.py +0 -0
  52. {programasweights-0.4.6 → programasweights-0.4.7}/examples/paw_monitor.py +0 -0
  53. {programasweights-0.4.6 → programasweights-0.4.7}/examples/replace_openai.py +0 -0
  54. {programasweights-0.4.6 → programasweights-0.4.7}/mkdocs.yml +0 -0
  55. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/_output.py +0 -0
  56. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/_program_reference.py +0 -0
  57. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/artifacts.py +0 -0
  58. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/cache.py +0 -0
  59. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/client.py +0 -0
  60. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/compiler/__init__.py +0 -0
  61. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/compiler/dummy.py +0 -0
  62. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/config.py +0 -0
  63. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/convert_peft_to_paw.py +0 -0
  64. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/errors.py +0 -0
  65. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/local_program.py +0 -0
  66. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/paw_format.py +0 -0
  67. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/runtime/__init__.py +0 -0
  68. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/runtime/interpreter.py +0 -0
  69. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/runtime/interpreter_onnx.py +0 -0
  70. {programasweights-0.4.6 → programasweights-0.4.7}/programasweights/runtime_llamacpp.py +0 -0
  71. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_api_errors.py +0 -0
  72. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_base_interpreter.py +0 -0
  73. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_cli_auth.py +0 -0
  74. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_compile_timeouts.py +0 -0
  75. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_desktop_sdk.py +0 -0
  76. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_local_program.py +0 -0
  77. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_offline_cache.py +0 -0
  78. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_runtime_registry_sdk.py +0 -0
  79. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_sdk.py +0 -0
  80. {programasweights-0.4.6 → programasweights-0.4.7}/tests/test_sdk.sh +0 -0
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: Publish and verify complete Python SDK releases
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # SDK releases
7
+
8
+ For release or publication work, read `RELEASING.md` first.
9
+
10
+ - A release is complete only when its annotated remote tag, PyPI wheel and
11
+ source distribution, and published GitHub Release all exist and agree.
12
+ - Build from the clean, tested release commit; push its annotated version tag
13
+ before uploading the exact checked artifacts to PyPI.
14
+ - Verify the `GitHub release` workflow succeeded and the latest GitHub Release
15
+ agrees with PyPI. A tag alone is not a GitHub Release entry.
16
+ - Repair missing entries using the documented workflow dispatch. Never move a
17
+ published tag, rebuild an existing version, or bypass artifact verification.
@@ -0,0 +1,72 @@
1
+ name: GitHub release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+ workflow_dispatch:
7
+ inputs:
8
+ tag:
9
+ description: 'Existing stable version tag, e.g. v0.4.6'
10
+ required: true
11
+ type: string
12
+
13
+ permissions:
14
+ contents: write
15
+
16
+ concurrency:
17
+ group: github-release-${{ inputs.tag || github.ref_name }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ release:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ env:
25
+ RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
26
+ GH_TOKEN: ${{ github.token }}
27
+ GH_REPO: ${{ github.repository }}
28
+ steps:
29
+ - name: Set the verification output directory
30
+ run: echo "RELEASE_DIR=$RUNNER_TEMP/verified-release" >> "$GITHUB_ENV"
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: '3.12'
37
+ - name: Verify the remote tag and both published PyPI artifacts
38
+ # Tags are pushed before uploading. Wait for the wheel AND sdist;
39
+ # never announce an unpublished version or rebuild release assets.
40
+ run: python scripts/release_metadata.py --tag "$RELEASE_TAG" --output-dir "$RELEASE_DIR" --wait-seconds 600
41
+ - name: Publish the missing GitHub release
42
+ shell: bash
43
+ run: |
44
+ set -euo pipefail
45
+ if gh release view "$RELEASE_TAG" --json tagName >/dev/null 2>&1; then
46
+ echo "Release already exists; preserving its notes and assets."
47
+ else
48
+ latest=$(jq -r .latest "$RELEASE_DIR/verified.json")
49
+ gh release create "$RELEASE_TAG" "$RELEASE_DIR"/artifacts/* \
50
+ --verify-tag --title "$RELEASE_TAG" \
51
+ --notes-file "$RELEASE_DIR/notes.md" --latest="$latest"
52
+ fi
53
+ gh release view "$RELEASE_TAG" --json tagName,isDraft,isPrerelease,url \
54
+ | jq -e --arg tag "$RELEASE_TAG" '.tagName == $tag and .isDraft == false and .isPrerelease == false'
55
+ - name: Verify the uploaded release assets and latest marker
56
+ shell: bash
57
+ run: |
58
+ set -euo pipefail
59
+ gh release download "$RELEASE_TAG" --pattern 'programasweights-*' \
60
+ --dir "$RELEASE_DIR/github-assets"
61
+ for artifact in "$RELEASE_DIR"/artifacts/*; do
62
+ cmp "$artifact" "$RELEASE_DIR/github-assets/$(basename "$artifact")"
63
+ done
64
+ if [ "$(jq -r .latest "$RELEASE_DIR/verified.json")" = true ]; then
65
+ gh api "repos/$GH_REPO/releases/latest" \
66
+ | jq -e --arg tag "$RELEASE_TAG" '.tag_name == $tag'
67
+ fi
68
+ - name: Retain the verification record
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: release-verification-${{ inputs.tag || github.ref_name }}
72
+ path: ${{ runner.temp }}/verified-release/verified.json
@@ -6,6 +6,17 @@ on:
6
6
  pull_request:
7
7
 
8
8
  jobs:
9
+ release-metadata:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: '3.12'
16
+ - run: python -m pip install pytest
17
+ - name: Test release verification without network or publishing
18
+ run: python -m pytest tests/test_release_metadata.py
19
+
9
20
  test:
10
21
  runs-on: ubuntu-latest
11
22
  strategy:
@@ -41,6 +52,7 @@ jobs:
41
52
  tests/test_local_program.py \
42
53
  tests/test_base_interpreter.py \
43
54
  tests/test_cli_auth.py \
55
+ tests/test_remote_inference.py \
44
56
  tests/test_desktop_sdk.py \
45
57
  tests/test_runtime_registry_sdk.py \
46
58
  tests/test_sdk.py::TestInstallAndImport
@@ -61,7 +73,7 @@ jobs:
61
73
  python -m pip install httpx pytest
62
74
  python -m pip install -e . --no-deps
63
75
  - name: Test Windows local paths, cache locks, and compile errors
64
- run: python -m pytest tests/test_api_errors.py tests/test_compile_timeouts.py tests/test_local_program.py --junitxml=test-results.xml
76
+ run: python -m pytest tests/test_api_errors.py tests/test_compile_timeouts.py tests/test_local_program.py tests/test_remote_inference.py --junitxml=test-results.xml
65
77
  - name: Annotate Windows test failures
66
78
  if: failure()
67
79
  shell: python
@@ -47,6 +47,42 @@ Load a local `.paw` file with `paw.function("./classifier.paw")` (SDK 0.4.5+).
47
47
 
48
48
  If you want the smaller browser-compatible runtime explicitly, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
49
49
 
50
+ ## Remote inference (optional)
51
+
52
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
53
+
54
+ ### Python SDK
55
+
56
+ ```python
57
+ import programasweights as paw
58
+
59
+ with paw.function("email-triage", remote=True) as remote_fn:
60
+ print(remote_fn("Urgent: the server is down!"))
61
+ ```
62
+
63
+ For authenticated requests, set `PAW_API_KEY` or use `paw.login()`.
64
+
65
+ ### Direct HTTP
66
+
67
+ Use `httpx` directly without installing the PAW SDK.
68
+
69
+ ```python
70
+ import httpx
71
+
72
+ with httpx.Client(timeout=60.0) as client:
73
+ response = client.post(
74
+ "https://programasweights.com/api/v1/infer",
75
+ json={
76
+ "program_id": "email-triage",
77
+ "input": "Urgent: server is down!"
78
+ },
79
+ )
80
+ response.raise_for_status()
81
+ print(response.json()["output"])
82
+ ```
83
+
84
+ For authenticated requests, pass `headers={"X-API-Key": api_key}` to `client.post()`.
85
+
50
86
  ## Current Public Compilers
51
87
 
52
88
  - **Standard** (`paw-4b-qwen3-0.6b`) — higher accuracy, 594 MB base + ~22 MB/program. This is the current server default.
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.7 (2026-09-20)
4
+
5
+ - Add `remote=True` to `paw.function` and `paw.compile_and_load`, plus
6
+ `paw run --remote`, for hosted inference without downloading model assets
7
+ or loading the local runtime. Local inference remains the default.
8
+ - Use server generation defaults unless explicitly overridden. Reuse HTTP
9
+ connections, support `with` and `.close()`, and preserve structured
10
+ `paw.APIError` details.
11
+ - Reject remote inference combined with offline mode or incompatible local
12
+ runtime options.
13
+
3
14
  ## 0.4.6 (2026-09-13)
4
15
 
5
16
  - Add an optional `logits_processor` argument to a compiled or base program
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: programasweights
3
- Version: 0.4.6
3
+ Version: 0.4.7
4
4
  Summary: Compile natural language specifications into neural programs that run locally via llama.cpp.
5
5
  Project-URL: Homepage, https://programasweights.com
6
6
  Project-URL: Repository, https://github.com/programasweights/programasweights-python
@@ -66,6 +66,19 @@ fn("I love this!") # "positive"
66
66
 
67
67
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
68
68
 
69
+ ## Remote inference (optional)
70
+
71
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
72
+
73
+ ```python
74
+ import programasweights as paw
75
+
76
+ with paw.function("email-triage", remote=True) as remote_fn:
77
+ print(remote_fn("Urgent: the server is down!"))
78
+ ```
79
+
80
+ For direct HTTP calls, see the [REST API reference](https://programasweights.readthedocs.io/en/latest/api-reference/rest-api/#post-infer).
81
+
69
82
  ## Current Public Compilers
70
83
 
71
84
 
@@ -189,6 +202,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
189
202
  paw compile --spec "Extract error lines from logs" --json
190
203
  paw run --program <program_id> --input "[ERROR] timeout" --json
191
204
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
205
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
192
206
  paw login
193
207
  ```
194
208
 
@@ -35,6 +35,19 @@ fn("I love this!") # "positive"
35
35
 
36
36
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
37
37
 
38
+ ## Remote inference (optional)
39
+
40
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
41
+
42
+ ```python
43
+ import programasweights as paw
44
+
45
+ with paw.function("email-triage", remote=True) as remote_fn:
46
+ print(remote_fn("Urgent: the server is down!"))
47
+ ```
48
+
49
+ For direct HTTP calls, see the [REST API reference](https://programasweights.readthedocs.io/en/latest/api-reference/rest-api/#post-infer).
50
+
38
51
  ## Current Public Compilers
39
52
 
40
53
 
@@ -158,6 +171,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
158
171
  paw compile --spec "Extract error lines from logs" --json
159
172
  paw run --program <program_id> --input "[ERROR] timeout" --json
160
173
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
174
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
161
175
  paw login
162
176
  ```
163
177
 
@@ -35,6 +35,19 @@ fn("I love this!") # "positive"
35
35
 
36
36
  If you specifically want the smaller browser-compatible runtime, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
37
37
 
38
+ ## Remote inference (optional)
39
+
40
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
41
+
42
+ ```python
43
+ import programasweights as paw
44
+
45
+ with paw.function("email-triage", remote=True) as remote_fn:
46
+ print(remote_fn("Urgent: the server is down!"))
47
+ ```
48
+
49
+ For direct HTTP calls, see the [REST API reference](docs/api-reference/rest-api.md#post-infer).
50
+
38
51
  ## Current Public Compilers
39
52
 
40
53
 
@@ -159,6 +172,7 @@ Generate API keys at [programasweights.com/settings](https://programasweights.co
159
172
  paw compile --spec "Extract error lines from logs" --json
160
173
  paw run --program <program_id> --input "[ERROR] timeout" --json
161
174
  paw run --program <program_id> --input "[ERROR] timeout" --offline --json
175
+ paw run --program <program_id> --input "[ERROR] timeout" --remote --json
162
176
  paw login
163
177
  ```
164
178
 
@@ -0,0 +1,82 @@
1
+ # Releasing the Python SDK
2
+
3
+ A release has three separate records: an annotated Git tag identifies the source,
4
+ PyPI serves the installable wheel and source distribution, and a GitHub Release
5
+ shows the version and release notes on the repository. A tag or PyPI upload alone
6
+ does not create a GitHub Release. Finish and verify all three before reporting a
7
+ release complete.
8
+
9
+ ## Publish a new version
10
+
11
+ 1. Update `pyproject.toml`, the fallback `__version__` in
12
+ `programasweights/__init__.py`, and the matching section in `CHANGELOG.md`.
13
+ Commit and push these changes to `main` using the existing user Git identity.
14
+ Release from a clean checkout of that pushed commit; confirm `HEAD` equals
15
+ `origin/main` and the `tests` workflow is green for that exact commit.
16
+ 2. Set the intended version, then build a wheel and source distribution into a
17
+ new, empty directory. Install `build` and `twine` in the release environment if
18
+ needed. Use the two exact filenames below, not a shared `dist/*` directory.
19
+
20
+ ```bash
21
+ SDK_RELEASE_VERSION=0.4.7 # replace with the version being released
22
+ SDK_RELEASE_DIR=$(mktemp -d)
23
+ python -m build --outdir "$SDK_RELEASE_DIR"
24
+ python -m twine check \
25
+ "$SDK_RELEASE_DIR/programasweights-$SDK_RELEASE_VERSION-py3-none-any.whl" \
26
+ "$SDK_RELEASE_DIR/programasweights-$SDK_RELEASE_VERSION.tar.gz"
27
+ ```
28
+
29
+ 3. Create and push the annotated version tag **before uploading to PyPI**. Check
30
+ that the remote tag resolves to the intended commit and is an annotated tag.
31
+ Never replace an existing release tag with a new target.
32
+
33
+ ```bash
34
+ git tag -a "v$SDK_RELEASE_VERSION" -m "Release v$SDK_RELEASE_VERSION"
35
+ git push origin "v$SDK_RELEASE_VERSION"
36
+ git ls-remote --tags origin "refs/tags/v$SDK_RELEASE_VERSION*"
37
+ ```
38
+
39
+ 4. Upload the exact wheel and source distribution that passed `twine check`:
40
+
41
+ ```bash
42
+ python -m twine upload \
43
+ "$SDK_RELEASE_DIR/programasweights-$SDK_RELEASE_VERSION-py3-none-any.whl" \
44
+ "$SDK_RELEASE_DIR/programasweights-$SDK_RELEASE_VERSION.tar.gz"
45
+ ```
46
+
47
+ 5. The `release.yml` workflow starts on the `v*` tag push. It waits up to 600
48
+ seconds for PyPI, verifies the remote annotated tag, version metadata, and
49
+ published wheel/source distribution against the tagged source, then creates
50
+ the GitHub Release with notes from that tag's `CHANGELOG.md` and the published
51
+ artifacts. It preserves an existing release and marks a new release as latest
52
+ only when its version is the latest on PyPI. If upload takes longer than the
53
+ wait window, dispatch the workflow again after PyPI publication succeeds.
54
+ 6. Verify the workflow succeeded, the GitHub Releases page has the expected entry
55
+ and notes, and the latest-release entry agrees with PyPI's latest version.
56
+ Compare both uploaded files' SHA-256 hashes with the version's PyPI JSON
57
+ (`https://pypi.org/pypi/programasweights/<version>/json`, `urls[].digests.sha256`)
58
+ and with the GitHub Release assets. Do not treat a tag listing as confirmation
59
+ that the release entry exists.
60
+
61
+ ## Recover a missing GitHub Release
62
+
63
+ When a version already exists on PyPI, keep its existing tag and published files.
64
+ Do not rebuild or republish that version, and never move its tag. Verify the
65
+ remote annotated tag, matching version metadata and changelog section, and the
66
+ published artifacts first. `scripts/release_metadata.py` requires Python 3.11+
67
+ and performs those checks, preparing the notes and assets in a new directory:
68
+
69
+ ```bash
70
+ SDK_RELEASE_RECOVERY_DIR=$(mktemp -d)
71
+ git fetch origin --tags
72
+ python scripts/release_metadata.py --tag v0.4.6 \
73
+ --output-dir "$SDK_RELEASE_RECOVERY_DIR/verified" --wait-seconds 600
74
+ gh workflow run release.yml --ref main -f tag=v0.4.6
75
+ ```
76
+
77
+ Replace `v0.4.6` with the verified existing tag to repair. Run this from current
78
+ `main`, which contains the recovery workflow and script; the release content is
79
+ read from the requested tag. The workflow is idempotent: an existing GitHub
80
+ Release is preserved. Recheck the release entry, latest-version status, and
81
+ artifact hashes after it finishes. A source or artifact mismatch needs
82
+ investigation; do not retag or overwrite published artifacts to make it pass.
@@ -33,10 +33,14 @@ paw compile --spec "Classify message urgency" [--compiler paw-4b-qwen3-0.6b] [--
33
33
 
34
34
  Run inference locally against a compiled program (the normal mode), or
35
35
  explicitly against a bare base interpreter (advanced mode).
36
+ Add `--remote` for hosted inference without downloading local model assets.
36
37
 
37
38
  ```bash
38
39
  paw run --program <id_or_slug> --input "your text" [--offline] [--json]
39
40
 
41
+ # Optional hosted inference
42
+ paw run --program email-triage --input "Urgent: the server is down!" --remote --json
43
+
40
44
  # Advanced adapter-free mode
41
45
  paw run --base --interpreter gpt2 --input "raw prompt" [--offline] [--json]
42
46
  ```
@@ -47,10 +51,11 @@ paw run --base --interpreter gpt2 --input "raw prompt" [--offline] [--json]
47
51
  | `--base` | Select adapter-free base mode. Mutually exclusive with `--program` and requires `--interpreter`. |
48
52
  | `--interpreter` | Base interpreter: `Qwen/Qwen3-0.6B` or `gpt2`. Only valid with `--base`. |
49
53
  | `--input` | Input text for the program. |
50
- | `--max-tokens` | Maximum tokens to generate (default: 512). |
51
- | `--temperature` | Sampling temperature (default: 0.0). |
54
+ | `--max-tokens` | Maximum tokens to generate. Local default: 512. Remote inference uses the server default when omitted. |
55
+ | `--temperature` | Sampling temperature. Local default: 0.0. Remote inference uses the server default when omitted. |
52
56
  | `--verbose` | Print llama.cpp debug output. |
53
57
  | `--offline` | Require all selected assets to already be cached and make zero network calls. |
58
+ | `--remote` | Use hosted inference for `--program`. Incompatible with `--base`, `--offline`, `--verbose`, and `PAW_OFFLINE=1`. |
54
59
  | `--json` | JSON output with `mode`, `program`, `interpreter`, `input`, and `output`. |
55
60
 
56
61
  Exactly one of `--program` and `--base` is required. An empty or
@@ -24,11 +24,13 @@ fn = paw.function(
24
24
  verbose=False,
25
25
  offline=False,
26
26
  *,
27
+ remote=False,
27
28
  interpreter=None,
28
29
  )
29
30
  ```
30
31
 
31
- Loads a compiled program and returns a callable. Hub references download the
32
+ Returns a callable for a compiled program. Local inference is the default.
33
+ Hub references download the
32
34
  program and base model on first use; local `.paw` files supply the program
33
35
  bundle directly. Required runtime metadata and base models are cached for reuse.
34
36
 
@@ -39,9 +41,10 @@ bundle directly. Required runtime metadata and base models are cached for reuse.
39
41
  | `n_gpu_layers` | GPU layers to offload (`0` = CPU-only, `-1` = all). The default is `-1`, or `PAW_GPU_LAYERS` when set. |
40
42
  | `verbose` | Enable verbose logging (default `False`). |
41
43
  | `offline` | Use only local files/cache and make zero network calls; fail if required validated assets are missing. `PAW_OFFLINE=1` has the same effect. |
44
+ | `remote` | Run hosted inference without downloading model assets (default `False`). Accepts a `Program` object, ID, or slug. Cannot be combined with offline mode, local file paths, `interpreter`, or non-default local runtime options. |
42
45
  | `interpreter` | Advanced adapter-free mode only. Must be passed by keyword and only when `program_id` is explicitly `None`. Supported values are `Qwen/Qwen3-0.6B` and `gpt2`. |
43
46
 
44
- The returned callable:
47
+ For local inference, the returned callable accepts:
45
48
 
46
49
  ```python
47
50
  output: str = fn(input_text, max_tokens=None, temperature=0.0, logits_processor=None)
@@ -67,6 +70,25 @@ same canonical integrity metadata, so missing server-side checksum fields
67
70
  cannot weaken validation. Missing or failed adapters raise an error; the SDK
68
71
  never silently falls back to an unadapted base model.
69
72
 
73
+ ### Remote inference
74
+
75
+ ```python
76
+ with paw.function("email-triage", remote=True) as remote_fn:
77
+ output = remote_fn("Urgent: the server is down!")
78
+ ```
79
+
80
+ The callable returns a string and accepts optional `max_tokens` and `temperature`.
81
+ Omitting either argument, or passing `None`, uses the server's default for that
82
+ setting. `logits_processor` is supported only for local inference.
83
+
84
+ Slugs are resolved once when the function is loaded. The `with` block closes
85
+ the HTTP client; otherwise, call `remote_fn.close()` when finished.
86
+
87
+ Remote calls use the existing [SDK configuration](#configuration) for the API
88
+ URL and API key. HTTP 4xx/5xx responses raise `paw.APIError`, preserving the
89
+ response status, headers, and structured error details. Transport errors
90
+ propagate unchanged.
91
+
70
92
  ### Loading a local `.paw` file
71
93
 
72
94
  Version 0.4.5 adds local-file inputs to `paw.function`:
@@ -274,12 +296,21 @@ attempt to load a function when compilation raises.
274
296
  ## `paw.compile_and_load`
275
297
 
276
298
  ```python
277
- fn = paw.compile_and_load(spec, compiler="paw-4b-qwen3-0.6b", **kwargs)
299
+ fn = paw.compile_and_load(spec, compiler=None, remote=False, **kwargs)
278
300
  ```
279
301
 
280
- Convenience method that compiles a spec and immediately loads the result for local inference. Equivalent to `paw.function(paw.compile(spec, ...).id)`. Returns a callable.
302
+ Compiles a spec and returns a callable. Inference runs locally by default.
303
+ Pass `remote=True` for hosted inference without downloading model assets.
304
+
305
+ Accepts the parameters of `paw.compile`, plus `n_ctx`, `n_gpu_layers`, `verbose`,
306
+ and `remote`. Local runtime options must retain their defaults with `remote=True`.
281
307
 
282
- Accepts all the same parameters as `paw.compile`.
308
+ ```python
309
+ with paw.compile_and_load(
310
+ "Classify sentiment as positive or negative", remote=True,
311
+ ) as remote_fn:
312
+ output = remote_fn("I love this!")
313
+ ```
283
314
 
284
315
  ## `paw.list_programs`
285
316
 
@@ -42,7 +42,10 @@ Compile a specification.
42
42
 
43
43
  ### `POST /infer`
44
44
 
45
- Run inference for a compiled program (server-side execution).
45
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
46
+
47
+ The Python SDK exposes hosted inference through `paw.function(..., remote=True)`.
48
+ See the [remote inference reference](python-sdk.md#remote-inference).
46
49
 
47
50
  **Request body (JSON):**
48
51
 
@@ -55,6 +58,23 @@ Run inference for a compiled program (server-side execution).
55
58
 
56
59
  **Response (JSON):** includes `output`, `tokens_generated`, and `latency_ms`.
57
60
 
61
+ **Example:**
62
+
63
+ ```python
64
+ import httpx
65
+
66
+ with httpx.Client(timeout=60.0) as client:
67
+ response = client.post(
68
+ "https://programasweights.com/api/v1/infer",
69
+ json={
70
+ "program_id": "email-triage",
71
+ "input": "Urgent: server is down!"
72
+ },
73
+ )
74
+ response.raise_for_status()
75
+ print(response.json()["output"])
76
+ ```
77
+
58
78
  ### `GET /programs`
59
79
 
60
80
  List or search programs.
@@ -20,6 +20,17 @@ print(result)
20
20
 
21
21
  The first call may download the program and runtime assets; later calls use the local cache.
22
22
 
23
+ ### Optional: remote inference
24
+
25
+ For fast inference without downloading model assets, pass `remote=True`:
26
+
27
+ ```python
28
+ with paw.function("email-triage", remote=True) as remote_fn:
29
+ print(remote_fn("Urgent: the server is down!"))
30
+ ```
31
+
32
+ For direct HTTP calls, see the [REST API reference](../api-reference/rest-api.md#post-infer).
33
+
23
34
  ## Step 2: Compile your own program
24
35
 
25
36
  Describe the behavior you want in natural language, compile it, then load the result by `program_id`:
@@ -16,6 +16,9 @@ The `--extra-index-url` flag provides pre-built binaries for `llama-cpp-python`,
16
16
  GPU offload defaults to all available layers (`n_gpu_layers=-1`). Set
17
17
  `PAW_GPU_LAYERS=0` to force CPU-only execution.
18
18
 
19
+ The same installation supports [remote inference](../api-reference/python-sdk.md#remote-inference)
20
+ with `paw.function(..., remote=True)`, without downloading local model assets.
21
+
19
22
  ## Anaconda on Linux: OpenMP / libgomp errors
20
23
 
21
24
  If the build fails with `libgomp`-related errors, disable OpenMP:
@@ -38,6 +38,20 @@ fn = paw.function(program.id)
38
38
  fn("{name: 'Alice', age: 30,}") # '{"name":"Alice","age":30}'
39
39
  ```
40
40
 
41
+ ## Remote inference (optional)
42
+
43
+ Use the hosted API for fast inference in around 150 ms, without a local model download.
44
+
45
+ ```python
46
+ import programasweights as paw
47
+
48
+ with paw.function("email-triage", remote=True) as remote_fn:
49
+ print(remote_fn("Urgent: the server is down!"))
50
+ ```
51
+
52
+ See the [Python SDK reference](api-reference/python-sdk.md#remote-inference)
53
+ or use the [REST API](api-reference/rest-api.md#post-infer) directly.
54
+
41
55
  ## What Can You Build?
42
56
 
43
57
  PAW is for functions that are easy to describe but hard to code as rules: