jevify 0.1.0__tar.gz → 0.3.0__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 (172) hide show
  1. jevify-0.3.0/.github/workflows/publish.yml +133 -0
  2. jevify-0.3.0/Makefile +50 -0
  3. {jevify-0.1.0 → jevify-0.3.0}/PKG-INFO +70 -10
  4. {jevify-0.1.0 → jevify-0.3.0}/README.md +69 -9
  5. {jevify-0.1.0 → jevify-0.3.0}/SKILL.md +53 -8
  6. {jevify-0.1.0 → jevify-0.3.0}/docs/00-invariants.md +2 -1
  7. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0002-backend-port-and-adapter-kinds.md +53 -4
  8. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0003-jev-compatible-api.md +30 -3
  9. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0004-recipes.md +1 -0
  10. jevify-0.3.0/docs/adr/0007-release-publishing.md +65 -0
  11. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/README.md +2 -1
  12. jevify-0.3.0/docs/playbook.md +303 -0
  13. {jevify-0.1.0 → jevify-0.3.0}/jevify/__init__.py +1 -1
  14. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/adapter.py +82 -30
  15. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/client.py +82 -0
  16. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/dialects.py +53 -7
  17. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/probe.py +175 -45
  18. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/readout.py +138 -9
  19. {jevify-0.1.0 → jevify-0.3.0}/jevify/cli.py +2 -2
  20. {jevify-0.1.0 → jevify-0.3.0}/jevify/compose.py +25 -30
  21. {jevify-0.1.0 → jevify-0.3.0}/jevify/domain/distribution.py +6 -5
  22. {jevify-0.1.0 → jevify-0.3.0}/jevify/ports/backend.py +5 -0
  23. {jevify-0.1.0 → jevify-0.3.0}/jevify/recipes/schema.py +61 -1
  24. {jevify-0.1.0 → jevify-0.3.0}/pyproject.toml +1 -1
  25. jevify-0.3.0/recipes/qwen38-27b.vllm.yaml +186 -0
  26. {jevify-0.1.0 → jevify-0.3.0}/tests/fakes/fake_openai_server.py +169 -4
  27. jevify-0.3.0/tests/fixtures/recipes/fake-tensorfold-messages.yaml +41 -0
  28. jevify-0.3.0/tests/fixtures/recipes/fake-tensorfold.yaml +39 -0
  29. jevify-0.3.0/tests/guards/test_request_fields.py +105 -0
  30. jevify-0.3.0/tests/guards/test_rung_registry.py +27 -0
  31. {jevify-0.1.0 → jevify-0.3.0}/tests/test_distribution.py +8 -3
  32. {jevify-0.1.0 → jevify-0.3.0}/tests/test_endpoint_adapter.py +61 -0
  33. jevify-0.3.0/tests/test_endpoint_client.py +112 -0
  34. jevify-0.3.0/tests/test_prompt_logprobs_messages.py +149 -0
  35. jevify-0.3.0/tests/test_prompt_logprobs_rung.py +149 -0
  36. {jevify-0.1.0 → jevify-0.3.0}/tests/test_recipes.py +75 -0
  37. jevify-0.3.0/tools/play_doom.py +393 -0
  38. {jevify-0.1.0 → jevify-0.3.0}/tools/record_doom_suite.py +7 -1
  39. {jevify-0.1.0 → jevify-0.3.0}/uv.lock +11 -11
  40. jevify-0.1.0/tools/play_doom.py +0 -209
  41. {jevify-0.1.0 → jevify-0.3.0}/.github/workflows/ci.yml +0 -0
  42. {jevify-0.1.0 → jevify-0.3.0}/.gitignore +0 -0
  43. {jevify-0.1.0 → jevify-0.3.0}/AGENTS.md +0 -0
  44. {jevify-0.1.0 → jevify-0.3.0}/LICENSE +0 -0
  45. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0001-founding-choices.md +0 -0
  46. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0005-evaluation-evidence-and-calibration.md +0 -0
  47. {jevify-0.1.0 → jevify-0.3.0}/docs/adr/0006-toolchain-dependency-budget-and-portability.md +0 -0
  48. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/bart-large-mnli.local--policy-29.json +0 -0
  49. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/bart-large-mnli.local--policy-29.md +0 -0
  50. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/bart-large-mnli.local--policy-hard-52.json +0 -0
  51. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/bart-large-mnli.local--policy-hard-52.md +0 -0
  52. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--calibration.json +0 -0
  53. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--calibration.md +0 -0
  54. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--doom-frames-41.json +0 -0
  55. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--doom-frames-41.md +0 -0
  56. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--long-state-24.json +0 -0
  57. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--long-state-24.md +0 -0
  58. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-29.json +0 -0
  59. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-29.md +0 -0
  60. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-hard-52.json +0 -0
  61. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/deepseek-v4-flash-vision-exp-keys.vllm--policy-hard-52.md +0 -0
  62. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--doom-frames-41.json +0 -0
  63. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--doom-frames-41.md +0 -0
  64. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--long-state-24.json +0 -0
  65. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--long-state-24.md +0 -0
  66. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-29.json +0 -0
  67. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-29.md +0 -0
  68. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-hard-52.json +0 -0
  69. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/gemma-4-e4b-it.llamacpp--policy-hard-52.md +0 -0
  70. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-29.json +0 -0
  71. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-29.md +0 -0
  72. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-hard-52.json +0 -0
  73. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-embedding-0.6b.local--policy-hard-52.md +0 -0
  74. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--doom-frames-41.json +0 -0
  75. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--doom-frames-41.md +0 -0
  76. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-29.json +0 -0
  77. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-29.md +0 -0
  78. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-hard-52.json +0 -0
  79. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-2b.llamacpp--policy-hard-52.md +0 -0
  80. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-8b.llamacpp--policy-29.json +0 -0
  81. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/qwen3-vl-reranker-8b.llamacpp--policy-29.md +0 -0
  82. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--doom-frames-41.json +0 -0
  83. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--doom-frames-41.md +0 -0
  84. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--long-state-24.json +0 -0
  85. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--long-state-24.md +0 -0
  86. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-29.json +0 -0
  87. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-29.md +0 -0
  88. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-hard-52.json +0 -0
  89. {jevify-0.1.0 → jevify-0.3.0}/docs/evidence/ternary-bonsai-2-27b.llamacpp--policy-hard-52.md +0 -0
  90. {jevify-0.1.0 → jevify-0.3.0}/jevify/__main__.py +0 -0
  91. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/__init__.py +0 -0
  92. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/embedding/__init__.py +0 -0
  93. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/embedding/adapter.py +0 -0
  94. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/embedding/local.py +0 -0
  95. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/encoder/__init__.py +0 -0
  96. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/encoder/adapter.py +0 -0
  97. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/encoder/local.py +0 -0
  98. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/endpoint/__init__.py +0 -0
  99. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/rerank/__init__.py +0 -0
  100. {jevify-0.1.0 → jevify-0.3.0}/jevify/adapters/rerank/adapter.py +0 -0
  101. {jevify-0.1.0 → jevify-0.3.0}/jevify/api/__init__.py +0 -0
  102. {jevify-0.1.0 → jevify-0.3.0}/jevify/api/app.py +0 -0
  103. {jevify-0.1.0 → jevify-0.3.0}/jevify/api/schema.py +0 -0
  104. {jevify-0.1.0 → jevify-0.3.0}/jevify/api/translate.py +0 -0
  105. {jevify-0.1.0 → jevify-0.3.0}/jevify/domain/__init__.py +0 -0
  106. {jevify-0.1.0 → jevify-0.3.0}/jevify/domain/engine.py +0 -0
  107. {jevify-0.1.0 → jevify-0.3.0}/jevify/domain/questions.py +0 -0
  108. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/__init__.py +0 -0
  109. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/calibrate.py +0 -0
  110. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/controls.py +0 -0
  111. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/metrics.py +0 -0
  112. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/scorecard.py +0 -0
  113. {jevify-0.1.0 → jevify-0.3.0}/jevify/evaluation/suites.py +0 -0
  114. {jevify-0.1.0 → jevify-0.3.0}/jevify/ports/__init__.py +0 -0
  115. {jevify-0.1.0 → jevify-0.3.0}/jevify/recipes/__init__.py +0 -0
  116. {jevify-0.1.0 → jevify-0.3.0}/jevify/recipes/probe_case.yaml +0 -0
  117. {jevify-0.1.0 → jevify-0.3.0}/jevify/recipes/render.py +0 -0
  118. {jevify-0.1.0 → jevify-0.3.0}/jevify/recipes/store.py +0 -0
  119. {jevify-0.1.0 → jevify-0.3.0}/recipes/bart-large-mnli.local.yaml +0 -0
  120. {jevify-0.1.0 → jevify-0.3.0}/recipes/deepseek-v4-flash-vision-exp-keys.vllm.yaml +0 -0
  121. {jevify-0.1.0 → jevify-0.3.0}/recipes/gemma-4-e4b-it.llamacpp.yaml +0 -0
  122. {jevify-0.1.0 → jevify-0.3.0}/recipes/qwen3-embedding-0.6b.local.yaml +0 -0
  123. {jevify-0.1.0 → jevify-0.3.0}/recipes/qwen3-vl-reranker-2b.llamacpp.yaml +0 -0
  124. {jevify-0.1.0 → jevify-0.3.0}/recipes/qwen3-vl-reranker-8b.llamacpp.yaml +0 -0
  125. {jevify-0.1.0 → jevify-0.3.0}/recipes/ternary-bonsai-2-27b.llamacpp.yaml +0 -0
  126. {jevify-0.1.0 → jevify-0.3.0}/suites/doom-frames-41/doom-frames-41.jsonl +0 -0
  127. {jevify-0.1.0 → jevify-0.3.0}/suites/doom-frames-41/doom-frames-41.manifest.json +0 -0
  128. {jevify-0.1.0 → jevify-0.3.0}/suites/long-state-24.jsonl +0 -0
  129. {jevify-0.1.0 → jevify-0.3.0}/suites/long-state-24.manifest.json +0 -0
  130. {jevify-0.1.0 → jevify-0.3.0}/suites/policy-29.jsonl +0 -0
  131. {jevify-0.1.0 → jevify-0.3.0}/suites/policy-29.manifest.json +0 -0
  132. {jevify-0.1.0 → jevify-0.3.0}/suites/policy-hard-52.jsonl +0 -0
  133. {jevify-0.1.0 → jevify-0.3.0}/suites/policy-hard-52.manifest.json +0 -0
  134. {jevify-0.1.0 → jevify-0.3.0}/tests/conftest.py +0 -0
  135. {jevify-0.1.0 → jevify-0.3.0}/tests/fakes/__init__.py +0 -0
  136. {jevify-0.1.0 → jevify-0.3.0}/tests/fakes/fake_backend.py +0 -0
  137. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/fake-embedding.yaml +0 -0
  138. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/fake-encoder.yaml +0 -0
  139. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/fake-raw.yaml +0 -0
  140. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/fake-rerank.yaml +0 -0
  141. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/fake-vllm.yaml +0 -0
  142. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/local-embedding.yaml +0 -0
  143. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/recipes/local-encoder.yaml +0 -0
  144. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/responses/llamacpp-completions-grammar.json +0 -0
  145. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/responses/llamacpp-completions-top_k.json +0 -0
  146. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/responses/llamacpp-native-completion.json +0 -0
  147. {jevify-0.1.0 → jevify-0.3.0}/tests/fixtures/responses/vllm-chat-top_k.json +0 -0
  148. {jevify-0.1.0 → jevify-0.3.0}/tests/guards/test_prompt_literals.py +0 -0
  149. {jevify-0.1.0 → jevify-0.3.0}/tests/guards/test_sdk_schema_mirror.py +0 -0
  150. {jevify-0.1.0 → jevify-0.3.0}/tests/guards/test_structure.py +0 -0
  151. {jevify-0.1.0 → jevify-0.3.0}/tests/live/test_live_ask.py +0 -0
  152. {jevify-0.1.0 → jevify-0.3.0}/tests/live/test_live_sdk.py +0 -0
  153. {jevify-0.1.0 → jevify-0.3.0}/tests/test_api.py +0 -0
  154. {jevify-0.1.0 → jevify-0.3.0}/tests/test_calibrate.py +0 -0
  155. {jevify-0.1.0 → jevify-0.3.0}/tests/test_classify_and_states.py +0 -0
  156. {jevify-0.1.0 → jevify-0.3.0}/tests/test_cli.py +0 -0
  157. {jevify-0.1.0 → jevify-0.3.0}/tests/test_controls.py +0 -0
  158. {jevify-0.1.0 → jevify-0.3.0}/tests/test_embedding_adapter.py +0 -0
  159. {jevify-0.1.0 → jevify-0.3.0}/tests/test_encoder_adapter.py +0 -0
  160. {jevify-0.1.0 → jevify-0.3.0}/tests/test_engine.py +0 -0
  161. {jevify-0.1.0 → jevify-0.3.0}/tests/test_evaluation.py +0 -0
  162. {jevify-0.1.0 → jevify-0.3.0}/tests/test_llamacpp_dialect.py +0 -0
  163. {jevify-0.1.0 → jevify-0.3.0}/tests/test_local_embedding.py +0 -0
  164. {jevify-0.1.0 → jevify-0.3.0}/tests/test_multimodal.py +0 -0
  165. {jevify-0.1.0 → jevify-0.3.0}/tests/test_probe.py +0 -0
  166. {jevify-0.1.0 → jevify-0.3.0}/tests/test_readout_ladder.py +0 -0
  167. {jevify-0.1.0 → jevify-0.3.0}/tests/test_rerank_adapter.py +0 -0
  168. {jevify-0.1.0 → jevify-0.3.0}/tests/test_sdk_acceptance.py +0 -0
  169. {jevify-0.1.0 → jevify-0.3.0}/tests/test_translate.py +0 -0
  170. {jevify-0.1.0 → jevify-0.3.0}/tests/test_warm.py +0 -0
  171. {jevify-0.1.0 → jevify-0.3.0}/tools/author_long_state.py +0 -0
  172. {jevify-0.1.0 → jevify-0.3.0}/tools/author_policy_hard.py +0 -0
@@ -0,0 +1,133 @@
1
+ # ADR-0007: a published GitHub release is built here from its tag and sent
2
+ # to PyPI by trusted publishing. No token is stored anywhere.
3
+ name: publish
4
+
5
+ on:
6
+ release:
7
+ types: [published]
8
+ # Dry run against an existing tag: builds, checks and compares, never publishes.
9
+ workflow_dispatch:
10
+ inputs:
11
+ tag:
12
+ description: "Release tag to build and check, e.g. v0.2.0"
13
+ required: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ concurrency:
19
+ group: publish-${{ inputs.tag || github.event.release.tag_name }}
20
+ cancel-in-progress: false
21
+
22
+ env:
23
+ TAG: ${{ inputs.tag || github.event.release.tag_name }}
24
+
25
+ jobs:
26
+ build:
27
+ name: build and check
28
+ runs-on: ubuntu-latest
29
+ outputs:
30
+ version: ${{ steps.version.outputs.version }}
31
+ attach: ${{ steps.compare.outputs.attach }}
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ ref: refs/tags/${{ env.TAG }}
36
+ - uses: astral-sh/setup-uv@v6
37
+ with:
38
+ python-version: "3.12"
39
+ - name: Tag matches the package version
40
+ id: version
41
+ run: |
42
+ version="$(uv version --short)"
43
+ if [ "v${version}" != "${TAG}" ]; then
44
+ echo "::error::tag ${TAG} does not match version ${version} in pyproject.toml"
45
+ exit 1
46
+ fi
47
+ echo "version=${version}" >> "$GITHUB_OUTPUT"
48
+ - name: Build
49
+ run: uv build
50
+ - name: twine check
51
+ run: uvx twine check --strict dist/*
52
+ - name: Install the wheel as a tool and run it from another directory
53
+ env:
54
+ VERSION: ${{ steps.version.outputs.version }}
55
+ run: |
56
+ uv tool install dist/*.whl
57
+ cd "${RUNNER_TEMP}"
58
+ got="$(jevify --version)"
59
+ if [ "${got}" != "jevify ${VERSION}" ]; then
60
+ echo "::error::installed wheel reports '${got}', expected 'jevify ${VERSION}'"
61
+ exit 1
62
+ fi
63
+ - name: Compare with the files attached to the release
64
+ id: compare
65
+ env:
66
+ GH_TOKEN: ${{ github.token }}
67
+ GH_REPO: ${{ github.repository }}
68
+ run: |
69
+ attached_names="$(gh release view "${TAG}" --json assets \
70
+ -q '.assets[].name | select(endswith(".whl") or endswith(".tar.gz"))')"
71
+ if [ -z "${attached_names}" ]; then
72
+ echo "No wheel or sdist attached to ${TAG}; this build is attached after publishing."
73
+ echo "attach=true" >> "$GITHUB_OUTPUT"
74
+ exit 0
75
+ fi
76
+ dir="${RUNNER_TEMP}/attached"
77
+ mkdir -p "${dir}"
78
+ gh release download "${TAG}" -p '*.whl' -p '*.tar.gz' -D "${dir}"
79
+ built="$(cd dist && sha256sum -- *.whl *.tar.gz)"
80
+ attached="$(cd "${dir}" && sha256sum -- *.whl *.tar.gz)"
81
+ if [ "${built}" != "${attached}" ]; then
82
+ echo "::error::the files attached to ${TAG} differ from this build of the tag"
83
+ echo "built:"; echo "${built}"
84
+ echo "attached:"; echo "${attached}"
85
+ exit 1
86
+ fi
87
+ echo "Attached files are byte-identical to this build:"
88
+ echo "${built}"
89
+ echo "attach=false" >> "$GITHUB_OUTPUT"
90
+ - uses: actions/upload-artifact@v4
91
+ with:
92
+ name: dist
93
+ path: dist/
94
+ if-no-files-found: error
95
+
96
+ publish:
97
+ name: publish to PyPI
98
+ needs: build
99
+ if: github.event_name == 'release'
100
+ runs-on: ubuntu-latest
101
+ environment:
102
+ name: pypi
103
+ url: https://pypi.org/project/jevify/${{ needs.build.outputs.version }}/
104
+ permissions:
105
+ id-token: write
106
+ steps:
107
+ - uses: actions/download-artifact@v4
108
+ with:
109
+ name: dist
110
+ path: dist/
111
+ - uses: pypa/gh-action-pypi-publish@release/v1
112
+
113
+ attach:
114
+ name: attach build to the release
115
+ needs: [build, publish]
116
+ if: github.event_name == 'release' && needs.build.outputs.attach == 'true'
117
+ runs-on: ubuntu-latest
118
+ permissions:
119
+ contents: write
120
+ steps:
121
+ - uses: actions/download-artifact@v4
122
+ with:
123
+ name: dist
124
+ path: dist/
125
+ - name: Upload wheel, sdist and SHA256SUMS
126
+ env:
127
+ GH_TOKEN: ${{ github.token }}
128
+ GH_REPO: ${{ github.repository }}
129
+ run: |
130
+ cd dist
131
+ sha256sum -- *.whl *.tar.gz > SHA256SUMS
132
+ cat SHA256SUMS
133
+ gh release upload "${TAG}" *.whl *.tar.gz SHA256SUMS
jevify-0.3.0/Makefile ADDED
@@ -0,0 +1,50 @@
1
+ # Shortcuts for the two everyday jevify commands: `make ask` asks typed questions
2
+ # about one state and prints the answer as JSON; `make serve` serves Jev's HTTP API
3
+ # for one recipe. Values reach jevify through the environment, so a state may hold
4
+ # spaces, quotes and apostrophes. `make help` lists the variables and the recipes.
5
+
6
+ JEVIFY ?= uv run jevify
7
+ HOST ?= 127.0.0.1
8
+ PORT ?= 8600
9
+
10
+ export RECIPE STATE CHOICE SCORE NOUL IMAGE RUNG HOST PORT
11
+
12
+ .DEFAULT_GOAL := help
13
+ .PHONY: help ask serve
14
+
15
+ help:
16
+ @echo "make ask RECIPE=<file> STATE=<text|@file> [CHOICE=...] [SCORE=...] [NOUL=...] [IMAGE=<png|jpg|webp>] [RUNG=<rung>]"
17
+ @echo "make serve RECIPE=<file> [HOST=$(HOST)] [PORT=$(PORT)]"
18
+ @echo ""
19
+ @echo " CHOICE id:question=option1,option2,... one option from a menu"
20
+ @echo " SCORE id:question=level0,level1,... a position on ordered levels, low to high"
21
+ @echo " NOUL id:statement the probability that the statement is true"
22
+ @echo " Give at least one of CHOICE, SCORE, NOUL. For several of one type, call jevify ask directly."
23
+ @echo " make reads \$$ in a value: write \$$\$$49.99 for \$$49.99, or put the text in a file and pass STATE=@path."
24
+ @echo " HOST other than 127.0.0.1 needs JEVIFY_API_KEY set: clients must then send it as a bearer key."
25
+ @echo ""
26
+ @echo "Example:"
27
+ @echo " make ask RECIPE=recipes/gemma-4-e4b-it.llamacpp.yaml \\"
28
+ @echo " STATE=\"Customer: I was charged twice. Fix it today or I cancel.\" \\"
29
+ @echo " CHOICE=\"dept:Which team should handle this?=billing,support,sales\" \\"
30
+ @echo " SCORE=\"urgency:How urgent is this?=low,medium,high\" \\"
31
+ @echo " NOUL=\"cancel:The customer threatens to cancel.\""
32
+ @echo ""
33
+ @echo "Recipes:"
34
+ @ls recipes/*.yaml | sed 's/^/ /'
35
+
36
+ ask:
37
+ @test -n "$$RECIPE" || { echo "make ask: set RECIPE=recipes/<name>.yaml (make help lists them)" >&2; exit 2; }
38
+ @test -n "$$STATE" || { echo "make ask: set STATE=\"...\" (the text, or @path to a file)" >&2; exit 2; }
39
+ @set -- --state "$$STATE"; \
40
+ [ -z "$$CHOICE" ] || set -- "$$@" --choice "$$CHOICE"; \
41
+ [ -z "$$SCORE" ] || set -- "$$@" --score "$$SCORE"; \
42
+ [ -z "$$NOUL" ] || set -- "$$@" --noul "$$NOUL"; \
43
+ [ -z "$$IMAGE" ] || set -- "$$@" --image "$$IMAGE"; \
44
+ [ -z "$$RUNG" ] || set -- "$$@" --rung "$$RUNG"; \
45
+ $(JEVIFY) ask "$$RECIPE" "$$@"
46
+
47
+ serve:
48
+ @test -n "$$RECIPE" || { echo "make serve: set RECIPE=recipes/<name>.yaml (make help lists them)" >&2; exit 2; }
49
+ @case "$$HOST" in 127.0.0.1|localhost|::1) ;; *) test -n "$$JEVIFY_API_KEY" || { echo "make serve: HOST=$$HOST opens the API to the network; export JEVIFY_API_KEY first" >&2; exit 2; } ;; esac
50
+ $(JEVIFY) serve "$$RECIPE" --host "$$HOST" --port "$$PORT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: jevify
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Make a pretrained model you already have answer typed questions in one pass, and measure how well it does.
5
5
  Project-URL: Homepage, https://github.com/fidecastro/jevify
6
6
  Author: Felipe Infante de Castro
@@ -39,7 +39,8 @@ Jev does, and measure how well it does.**
39
39
  [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
40
40
 
41
41
  You bring a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp,
42
- anything with logprobs) or a small model that runs in-process. jevify sends it
42
+ TensorFold, anything that returns logprobs for a reply token or for the
43
+ prompt) or a small model that runs in-process. jevify sends it
43
44
  a **state** once, then asks **typed questions** about it: a choice among
44
45
  options, a score on ordered levels, a yes/no with a probability. Each answer
45
46
  is read straight off the model's next-token distribution, no generation, no
@@ -81,10 +82,36 @@ jevify ask recipes/gemma-4-e4b-it.llamacpp.yaml \
81
82
  jevify serve recipes/gemma-4-e4b-it.llamacpp.yaml --port 8600
82
83
  ```
83
84
 
85
+ Any model behind an OpenAI-compatible server works, not only the ones under
86
+ `recipes/`: copy the nearest recipe, set `endpoint.base_url` and
87
+ `model.name`, and run the probe, which re-verifies the answer token ids for
88
+ that model and writes what it found into the file. vLLM and llama.cpp get
89
+ their dialect extras; any other server is driven through the plain OpenAI
90
+ surface. A server that refuses logprobs on the reply token but returns
91
+ vLLM's `prompt_logprobs`, such as TensorFold's native server with
92
+ GLM-5.3-Flash, is read through the `prompt_logprobs` rung on
93
+ `/v1/completions`: with a raw-mode recipe (the template written out with the
94
+ model's own control tokens), or with a chat-template recipe where the server
95
+ also takes `messages` on that route, as TensorFold's does. The chat form is
96
+ the one that carries images.
97
+
98
+ `serve` binds to `127.0.0.1`. To reach it from other machines pass
99
+ `--host 0.0.0.0`, and set `JEVIFY_API_KEY` first so that every request must
100
+ carry that bearer key; to leave it running, detach it and keep its log:
101
+
102
+ ```sh
103
+ jevify serve recipes/<name>.yaml --host 0.0.0.0 --port 8600 > ~/jevify.log 2>&1 &
104
+ curl -s http://<this-host>:8600/health
105
+ ```
106
+
84
107
  Every answer carries Jev's fields (`choice`, `confidence`, `probabilities`,
85
108
  `score`, `legend`, `noul`) plus an `x_jevify` block naming the readout method,
86
109
  the cached tokens, the latency and the recipe hash that produced it.
87
110
 
111
+ From a clone, `make ask` and `make serve` wrap the two commands;
112
+ [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md)
113
+ walks through them and explains every answer field.
114
+
88
115
  ## How it works
89
116
 
90
117
  1. **Recipe.** A hashed YAML file: the endpoint, the prompt template (chat
@@ -99,7 +126,9 @@ the cached tokens, the latency and the recipe hash that produced it.
99
126
  that reuses the server's prefix cache. A 32k-token state costs one warm and
100
127
  then a few hundred milliseconds per question.
101
128
  4. **Readout ladder.** Named-token logprobs, grammar with post-sampling
102
- probabilities, top-k, equal bias, and a floored top-k as the last resort.
129
+ probabilities, top-k, prompt logprobs (each answer token appended to the
130
+ prompt and its logprob read back, for servers that give none on the reply
131
+ token), equal bias, and a floored top-k as the last resort.
103
132
  Auto walks down the proven rungs; the answer names the rung that read it
104
133
  and whether it was degraded.
105
134
  5. **Evidence.** `jevify eval` runs a frozen suite with controls (shuffled
@@ -110,7 +139,7 @@ the cached tokens, the latency and the recipe hash that produced it.
110
139
 
111
140
  | Kind | Talks to | Proven live with |
112
141
  |---|---|---|
113
- | `endpoint` | Any OpenAI-compatible server; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
142
+ | `endpoint` | Any OpenAI-compatible server that returns reply-token or prompt logprobs; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
114
143
  | `embedding` | A `/v1/embeddings` route, or a model in-process | Qwen3-Embedding-0.6B in-process |
115
144
  | `encoder` | An NLI sequence-classification head in-process | facebook/bart-large-mnli in-process |
116
145
  | `rerank` | A `/v1/rerank` route | Fakes only |
@@ -123,6 +152,28 @@ Four frozen suites, every recipe on each, one run each, latencies per
123
152
  question on one machine on one day. Every row links to the scorecard that
124
153
  records the command, the recipe hash, the suite hash and the raw file digest.
125
154
 
155
+ ### Latency per question
156
+
157
+ The median of each suite, combined across the suites a model ran; one run
158
+ each. A question is one request that reads the answer off the next-token
159
+ probabilities, after the state was sent once and cached.
160
+
161
+ | Model | How it runs | Hardware | Latency per question |
162
+ |---|---|---|---|
163
+ | facebook/bart-large-mnli | in-process NLI classifier | RTX 4090 | 7–9 ms, avg 8 |
164
+ | Qwen3-Embedding-0.6B | in-process embedding, cosine | RTX 4090 | 8–10 ms, avg 9 |
165
+ | Gemma 4 E4B (Q8_0) | llama.cpp, chat | RTX 4090 | 28–37 ms, avg 32 |
166
+ | Qwen3-VL-Reranker-2B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 47–64 ms, avg 53 |
167
+ | Qwen3-VL-Reranker-8B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 68 ms (one suite) |
168
+ | Ternary Bonsai 2 27B (PQ2_0) | llama.cpp (PrismML fork), raw prompt | RTX 4090 | 97–113 ms, avg 102 |
169
+ | DeepSeek-V4-Flash | vLLM, chat | 2× DGX Spark, over LAN | 359–672 ms, avg 483 |
170
+
171
+ Image questions cost the llama.cpp models nothing extra, since the frame is
172
+ paid once in the warm; DeepSeek pays about half again on images. The two
173
+ in-process models are the fastest and the least accurate.
174
+
175
+ ### Accuracy by suite
176
+
126
177
  **policy-29**, the diagnostic suite carried over from the author's earlier experiments (choice only):
127
178
 
128
179
  | Recipe | Kind | Accuracy | Median latency | Scorecard |
@@ -221,6 +272,8 @@ jevify is written to be operated through a coding agent. Point yours at
221
272
  | [`tools/`](https://github.com/fidecastro/jevify/blob/main/tools) | Suite authoring, the Doom recorder and player |
222
273
  | [`AGENTS.md`](https://github.com/fidecastro/jevify/blob/main/AGENTS.md) | The engineering principles that bind every change |
223
274
  | [`SKILL.md`](https://github.com/fidecastro/jevify/blob/main/SKILL.md) | How an agent operates jevify |
275
+ | [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md) | How a person operates jevify: ask, read an answer, serve, add a model, fix common problems |
276
+ | [`Makefile`](https://github.com/fidecastro/jevify/blob/main/Makefile) | `make ask` and `make serve` shortcuts; `make help` lists the variables and recipes |
224
277
 
225
278
  To work on jevify: `uv sync --group dev` (add `--extra encoder --extra
226
279
  games` as needed) and `uv run pytest`. Live and in-process tests skip unless
@@ -229,14 +282,21 @@ you opt in.
229
282
  ## Not yet
230
283
 
231
284
  Named so that nobody has to discover it: a direct-call control, so no
232
- "faster than generation" claim exists yet; a Doom player that survives (both
233
- models die within seconds of `deadly_corridor`); the rerank kind and the
285
+ "faster than generation" claim exists yet; clearing Doom's `deadly_corridor`
286
+ (in `defend_the_center` Bonsai's perception plus a fixed rule scores 10 kills
287
+ in 16 seconds, but in the corridor even the rule fed the engine's own labels
288
+ dies before the armour, so the corridor needs a policy with memory, not
289
+ better perception); the rerank kind and the
234
290
  embedding route against a live server; a marker-slot encoder layout; the
235
291
  grammar rung on the PrismML llama.cpp fork, which accepts a grammar without
236
- constraining the reported probabilities; Windows. Choice menus on the
237
- endpoint kind are bounded by the single-token identifier alphabet the probe
238
- verifies (26 on the DeepSeek recipe); Jev's 255-option ceiling is not
239
- reachable there.
292
+ constraining the reported probabilities; the `prompt_logprobs` rung on any
293
+ server but TensorFold's native one with GLM-5.3-Flash, where it has been
294
+ probed and asked by hand (raw and chat recipes, text and images) but no
295
+ evaluation suite has been scored on it yet (that server skips its prefix
296
+ cache for such requests, so every question reads the whole state again,
297
+ images included, which the probe measures and notes); Windows. Choice menus on the endpoint kind are bounded by the
298
+ single-token identifier alphabet the probe verifies (26 on the DeepSeek
299
+ recipe); Jev's 255-option ceiling is not reachable there.
240
300
 
241
301
  ## Licence
242
302
 
@@ -9,7 +9,8 @@ Jev does, and measure how well it does.**
9
9
  [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
10
10
 
11
11
  You bring a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp,
12
- anything with logprobs) or a small model that runs in-process. jevify sends it
12
+ TensorFold, anything that returns logprobs for a reply token or for the
13
+ prompt) or a small model that runs in-process. jevify sends it
13
14
  a **state** once, then asks **typed questions** about it: a choice among
14
15
  options, a score on ordered levels, a yes/no with a probability. Each answer
15
16
  is read straight off the model's next-token distribution, no generation, no
@@ -51,10 +52,36 @@ jevify ask recipes/gemma-4-e4b-it.llamacpp.yaml \
51
52
  jevify serve recipes/gemma-4-e4b-it.llamacpp.yaml --port 8600
52
53
  ```
53
54
 
55
+ Any model behind an OpenAI-compatible server works, not only the ones under
56
+ `recipes/`: copy the nearest recipe, set `endpoint.base_url` and
57
+ `model.name`, and run the probe, which re-verifies the answer token ids for
58
+ that model and writes what it found into the file. vLLM and llama.cpp get
59
+ their dialect extras; any other server is driven through the plain OpenAI
60
+ surface. A server that refuses logprobs on the reply token but returns
61
+ vLLM's `prompt_logprobs`, such as TensorFold's native server with
62
+ GLM-5.3-Flash, is read through the `prompt_logprobs` rung on
63
+ `/v1/completions`: with a raw-mode recipe (the template written out with the
64
+ model's own control tokens), or with a chat-template recipe where the server
65
+ also takes `messages` on that route, as TensorFold's does. The chat form is
66
+ the one that carries images.
67
+
68
+ `serve` binds to `127.0.0.1`. To reach it from other machines pass
69
+ `--host 0.0.0.0`, and set `JEVIFY_API_KEY` first so that every request must
70
+ carry that bearer key; to leave it running, detach it and keep its log:
71
+
72
+ ```sh
73
+ jevify serve recipes/<name>.yaml --host 0.0.0.0 --port 8600 > ~/jevify.log 2>&1 &
74
+ curl -s http://<this-host>:8600/health
75
+ ```
76
+
54
77
  Every answer carries Jev's fields (`choice`, `confidence`, `probabilities`,
55
78
  `score`, `legend`, `noul`) plus an `x_jevify` block naming the readout method,
56
79
  the cached tokens, the latency and the recipe hash that produced it.
57
80
 
81
+ From a clone, `make ask` and `make serve` wrap the two commands;
82
+ [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md)
83
+ walks through them and explains every answer field.
84
+
58
85
  ## How it works
59
86
 
60
87
  1. **Recipe.** A hashed YAML file: the endpoint, the prompt template (chat
@@ -69,7 +96,9 @@ the cached tokens, the latency and the recipe hash that produced it.
69
96
  that reuses the server's prefix cache. A 32k-token state costs one warm and
70
97
  then a few hundred milliseconds per question.
71
98
  4. **Readout ladder.** Named-token logprobs, grammar with post-sampling
72
- probabilities, top-k, equal bias, and a floored top-k as the last resort.
99
+ probabilities, top-k, prompt logprobs (each answer token appended to the
100
+ prompt and its logprob read back, for servers that give none on the reply
101
+ token), equal bias, and a floored top-k as the last resort.
73
102
  Auto walks down the proven rungs; the answer names the rung that read it
74
103
  and whether it was degraded.
75
104
  5. **Evidence.** `jevify eval` runs a frozen suite with controls (shuffled
@@ -80,7 +109,7 @@ the cached tokens, the latency and the recipe hash that produced it.
80
109
 
81
110
  | Kind | Talks to | Proven live with |
82
111
  |---|---|---|
83
- | `endpoint` | Any OpenAI-compatible server; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
112
+ | `endpoint` | Any OpenAI-compatible server that returns reply-token or prompt logprobs; dialect extras for vLLM and llama.cpp | DeepSeek-V4-Flash on vLLM; Ternary Bonsai 2 27B, Gemma 4 E4B and Qwen3-VL-Reranker 8B/2B on llama.cpp; text and images |
84
113
  | `embedding` | A `/v1/embeddings` route, or a model in-process | Qwen3-Embedding-0.6B in-process |
85
114
  | `encoder` | An NLI sequence-classification head in-process | facebook/bart-large-mnli in-process |
86
115
  | `rerank` | A `/v1/rerank` route | Fakes only |
@@ -93,6 +122,28 @@ Four frozen suites, every recipe on each, one run each, latencies per
93
122
  question on one machine on one day. Every row links to the scorecard that
94
123
  records the command, the recipe hash, the suite hash and the raw file digest.
95
124
 
125
+ ### Latency per question
126
+
127
+ The median of each suite, combined across the suites a model ran; one run
128
+ each. A question is one request that reads the answer off the next-token
129
+ probabilities, after the state was sent once and cached.
130
+
131
+ | Model | How it runs | Hardware | Latency per question |
132
+ |---|---|---|---|
133
+ | facebook/bart-large-mnli | in-process NLI classifier | RTX 4090 | 7–9 ms, avg 8 |
134
+ | Qwen3-Embedding-0.6B | in-process embedding, cosine | RTX 4090 | 8–10 ms, avg 9 |
135
+ | Gemma 4 E4B (Q8_0) | llama.cpp, chat | RTX 4090 | 28–37 ms, avg 32 |
136
+ | Qwen3-VL-Reranker-2B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 47–64 ms, avg 53 |
137
+ | Qwen3-VL-Reranker-8B (Q8_0) | llama.cpp, yes/no readout | RTX 4090 | 68 ms (one suite) |
138
+ | Ternary Bonsai 2 27B (PQ2_0) | llama.cpp (PrismML fork), raw prompt | RTX 4090 | 97–113 ms, avg 102 |
139
+ | DeepSeek-V4-Flash | vLLM, chat | 2× DGX Spark, over LAN | 359–672 ms, avg 483 |
140
+
141
+ Image questions cost the llama.cpp models nothing extra, since the frame is
142
+ paid once in the warm; DeepSeek pays about half again on images. The two
143
+ in-process models are the fastest and the least accurate.
144
+
145
+ ### Accuracy by suite
146
+
96
147
  **policy-29**, the diagnostic suite carried over from the author's earlier experiments (choice only):
97
148
 
98
149
  | Recipe | Kind | Accuracy | Median latency | Scorecard |
@@ -191,6 +242,8 @@ jevify is written to be operated through a coding agent. Point yours at
191
242
  | [`tools/`](https://github.com/fidecastro/jevify/blob/main/tools) | Suite authoring, the Doom recorder and player |
192
243
  | [`AGENTS.md`](https://github.com/fidecastro/jevify/blob/main/AGENTS.md) | The engineering principles that bind every change |
193
244
  | [`SKILL.md`](https://github.com/fidecastro/jevify/blob/main/SKILL.md) | How an agent operates jevify |
245
+ | [`docs/playbook.md`](https://github.com/fidecastro/jevify/blob/main/docs/playbook.md) | How a person operates jevify: ask, read an answer, serve, add a model, fix common problems |
246
+ | [`Makefile`](https://github.com/fidecastro/jevify/blob/main/Makefile) | `make ask` and `make serve` shortcuts; `make help` lists the variables and recipes |
194
247
 
195
248
  To work on jevify: `uv sync --group dev` (add `--extra encoder --extra
196
249
  games` as needed) and `uv run pytest`. Live and in-process tests skip unless
@@ -199,14 +252,21 @@ you opt in.
199
252
  ## Not yet
200
253
 
201
254
  Named so that nobody has to discover it: a direct-call control, so no
202
- "faster than generation" claim exists yet; a Doom player that survives (both
203
- models die within seconds of `deadly_corridor`); the rerank kind and the
255
+ "faster than generation" claim exists yet; clearing Doom's `deadly_corridor`
256
+ (in `defend_the_center` Bonsai's perception plus a fixed rule scores 10 kills
257
+ in 16 seconds, but in the corridor even the rule fed the engine's own labels
258
+ dies before the armour, so the corridor needs a policy with memory, not
259
+ better perception); the rerank kind and the
204
260
  embedding route against a live server; a marker-slot encoder layout; the
205
261
  grammar rung on the PrismML llama.cpp fork, which accepts a grammar without
206
- constraining the reported probabilities; Windows. Choice menus on the
207
- endpoint kind are bounded by the single-token identifier alphabet the probe
208
- verifies (26 on the DeepSeek recipe); Jev's 255-option ceiling is not
209
- reachable there.
262
+ constraining the reported probabilities; the `prompt_logprobs` rung on any
263
+ server but TensorFold's native one with GLM-5.3-Flash, where it has been
264
+ probed and asked by hand (raw and chat recipes, text and images) but no
265
+ evaluation suite has been scored on it yet (that server skips its prefix
266
+ cache for such requests, so every question reads the whole state again,
267
+ images included, which the probe measures and notes); Windows. Choice menus on the endpoint kind are bounded by the
268
+ single-token identifier alphabet the probe verifies (26 on the DeepSeek
269
+ recipe); Jev's 255-option ceiling is not reachable there.
210
270
 
211
271
  ## Licence
212
272
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jevify
3
- description: Operate jevify, the bring-your-own-model harness that makes a pretrained model answer Jev-shaped typed questions (choice, score, noul) over a shared state and measures how well it does. Use when asked to connect a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp, others) or an in-process encoder or embedding model, write or probe a recipe, ask questions, run a suite, read a scorecard, calibrate, or serve Jev's API.
3
+ description: Operate jevify, the bring-your-own-model harness that makes a pretrained model answer Jev-shaped typed questions (choice, score, noul) over a shared state and measures how well it does. Use when asked to connect a model behind an OpenAI-compatible endpoint (vLLM, llama.cpp, TensorFold, others) or an in-process encoder or embedding model, write or probe a recipe, ask questions, run a suite, read a scorecard, calibrate, or serve Jev's API.
4
4
  ---
5
5
 
6
6
  # Operating jevify
@@ -31,7 +31,9 @@ result.
31
31
  rows carry `images`, regenerate the frames with
32
32
  `uv run python tools/record_doom_suite.py` after `uv sync --extra games`),
33
33
  `long-state-24` (2k to 32k tokens). `tools/play_doom.py <recipe> --scenario
34
- defend_the_center --policy perception` records a video of a recipe playing.
34
+ defend_the_center --policy perception` records a video of a recipe playing;
35
+ `--policy expert` replays the same rule from the engine's labels, the
36
+ ceiling any perception can reach with that rule.
35
37
  Raw decisions land in `runs/` (git-ignored); the summary in `docs/evidence/`
36
38
  (committed). Read the Markdown summary, then the `wrong` list in the JSON.
37
39
  5. **Serve**: `jevify serve recipes/<name>.yaml --port 8600`. Jev's SDK works
@@ -51,15 +53,37 @@ Every recipe has `schema_version: 1`, `model: {name, kind, revision}` and
51
53
  the rest.
52
54
 
53
55
  **`endpoint`** (any OpenAI-compatible server): `endpoint: {base_url,
54
- api_key_env, dialect: auto|vllm|llamacpp|generic, timeout_s}`, `template`
55
- and `answers`.
56
+ api_key_env, dialect: auto|vllm|llamacpp|generic, timeout_s, extra_body,
57
+ headers, headers_env}`, `template` and `answers`.
56
58
  - `template.mode: messages` sends chat messages; put thinking off in
57
59
  `template.kwargs` (`{thinking: false}` for DeepSeek, `{enable_thinking:
58
60
  false}` for Qwen3-class models; names are model-specific, the probe checks
59
61
  that no `<think>` token opens the answer).
62
+ - `endpoint.extra_body` is merged into the top level of every request, for
63
+ servers that take a switch there rather than as a template argument. The
64
+ `generic` dialect sends no template arguments, so on Ollama's `/v1` route
65
+ a thinking model needs `extra_body: {reasoning_effort: none}` (`think:
66
+ false` is ignored there). A key jevify itself sets (`max_tokens`,
67
+ `logprobs`, `logit_bias`, ...) is refused when the backend is built, before
68
+ any request, so probe, ask, eval and serve all fail the same way. The
69
+ fields also reach the tokenize and template routes the probe uses.
70
+ - `endpoint.headers` adds static HTTP headers to every request, for a router
71
+ or gateway that requires one (`{x-client-id: agenticApp}`). The values are
72
+ written in the recipe and hashed with it, so never put a secret there: a
73
+ bearer key goes in the env var that `api_key_env` names, and any other
74
+ secret header in `endpoint.headers_env` (header name to env var name, read
75
+ when the backend is built; an unset variable fails then). A static header
76
+ whose name looks like a credential (`key`, `token`, `secret`, `auth`,
77
+ `cookie`, ...) is refused when the recipe loads; `Content-Type` and
78
+ `Authorization` are refused when the backend is built. Every
79
+ endpoint-backed kind sends them (endpoint, rerank, embedding).
60
80
  - `template.mode: raw` sends a completion prompt you write with the model's
61
81
  own control tokens; use it when the chat template would not put the model
62
- in its trained regime (the Qwen3-VL reranker recipe is the example).
82
+ in its trained regime (the Qwen3-VL reranker recipe is the example), and
83
+ for a server that refuses reply-token logprobs (TensorFold's native server
84
+ with GLM-5.3-Flash) when the state is text. The `prompt_logprobs` rung also
85
+ reads a messages-mode recipe where the server takes `messages` on
86
+ `/v1/completions` (TensorFold's does); that is the form that carries images.
63
87
  - `template.questions.choice_strategy`: `identifiers` (one request, options
64
88
  labelled A, B, C) or `per_option` (one yes/no request per option; needed
65
89
  for a model that never emits letters, such as a reranker).
@@ -67,8 +91,12 @@ and `answers`.
67
91
  the probe fills their ids.
68
92
  - `readout.rung: auto` walks the rungs the probe proved, strongest first:
69
93
  `named_token_logprobs` (vLLM with `logprob_token_ids`), `grammar`
70
- (llama.cpp), `top_k`, `equal_bias`, `top_k_floor` (degraded, missing
71
- labels floored and listed). A rung that cannot see every label hands the
94
+ (llama.cpp), `top_k`, `prompt_logprobs` (each answer spelling appended to
95
+ a raw prompt, or sent as the assistant's reply in messages mode, and read
96
+ back from vLLM-style `prompt_logprobs` rows, one
97
+ request more for every spelling the rows did not show; exact, off-menu mass
98
+ visible), `equal_bias`, `top_k_floor` (degraded, missing labels floored and
99
+ listed). A rung that cannot see every label hands the
72
100
  question to the next; the answer names the rung that read it. A pinned rung
73
101
  never walks: it fails loudly instead.
74
102
 
@@ -88,7 +116,10 @@ sequence-classification head; all three question types; semantics `readout`.
88
116
  ## Reading an answer
89
117
 
90
118
  Jev's fields come first (`choice`, `confidence`, `probabilities`; `score`,
91
- `legend`; `noul`). Everything jevify adds is under `x_jevify`:
119
+ `legend`; `noul`). `confidence` is Jev's statistic, `(n × peak − 1) / (n − 1)`
120
+ over the options or levels: 0 when uniform, 1 when certain, and the number
121
+ TypeSafe's published thresholds refer to. Everything jevify adds is under
122
+ `x_jevify`:
92
123
 
93
124
  | Field | Meaning |
94
125
  |---|---|
@@ -138,6 +169,20 @@ Jev's fields come first (`choice`, `confidence`, `probabilities`; `score`,
138
169
  checkpoint at the end of an earlier prompt, and a chat template closes the
139
170
  user turn right after the state. Use `template.mode: raw` so the warm prompt
140
171
  ends at the state boundary; the Bonsai recipe is the example.
172
+ - **Probe notes "reply-token logprobs refused"**: the server gives no
173
+ logprobs on a generated token (TensorFold with GLM-5.3-Flash). The probe
174
+ then tries `prompt_logprobs`: a raw recipe on any such server, a chat
175
+ recipe where the server takes `messages` on `/v1/completions`; elsewhere a
176
+ chat recipe ends in "no readout is possible".
177
+ - **"is not at the prompt row the probe measured"**: in messages mode the
178
+ server's template renders the answer turn differently than when the probe
179
+ ran (thinking switched on or off, a new template); re-run the probe.
180
+ - **"did not come back as its own prompt token"**: the appended answer fuses
181
+ with the end of the raw template; end the template where the answer starts
182
+ a token of its own (after a newline, or before a leading-space spelling).
183
+ - **Probe notes "prompt_logprobs requests reuse no cached prefix"**: the
184
+ server reads the whole state again for every question on that rung; keep
185
+ states short there, or serve the model where reply-token logprobs exist.
141
186
  - **Missing encoder extra**: the error names the install command.
142
187
 
143
188
  ## House rules that bind you here
@@ -126,7 +126,7 @@ API response may claim more than it permits.
126
126
 
127
127
  Rules that follow:
128
128
 
129
- - **Confidence** is a statistic of the returned distribution: one minus its normalized entropy for choice and score, distance from one half for noul. It is not an independent estimate that the decision is right, and copy may not describe it as one.
129
+ - **Confidence** is Jev's statistic of the returned distribution: `(n × peak − 1) / (n − 1)` over the n options or levels for choice and score, so a uniform distribution scores 0 and a certain one scores 1 (TypeSafe's published definition). Jev's noul answers carry no confidence; jevify reports the distance of P(true) from one half, scaled to [0, 1], under `x_jevify` only. It is not an independent estimate that the decision is right, and copy may not describe it as one.
130
130
  - **"Faster"** may be claimed only against a measured direct call on the same backend, same model, same state, with the measurement in a scorecard.
131
131
  - **"Accuracy"** names its suite and the suite's hash. A suite whose expected answers came from a teacher model measures agreement with that teacher, and copy says so.
132
132
  - **A fine-tuned result is never compared with a zero-shot result in one table** without the words fine-tuned and zero-shot in the row.
@@ -150,6 +150,7 @@ number and the previous wording. The edit and the ADR land in the same commit.
150
150
  | Date | Invariant | ADR | Change |
151
151
  |---|---|---|---|
152
152
  | 2026-09-19 | all | ADR-0001 | Adopted. |
153
+ | 2026-09-23 | §5, confidence rule | ADR-0003 D8 | Confidence is Jev's peak statistic, `(n × peak − 1) / (n − 1)`. Previous wording: "a statistic of the returned distribution: one minus its normalized entropy for choice and score, distance from one half for noul". The entropy statistic disagreed with Jev's published definition, so the SDK-facing `confidence` field did not carry Jev's meaning. |
153
154
 
154
155
  ## 8. Document map
155
156