loopmath 0.2.6__tar.gz → 0.2.8__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.
- {loopmath-0.2.6/src/loopmath.egg-info → loopmath-0.2.8}/PKG-INFO +3 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/README.md +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/pyproject.toml +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/__init__.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/atrium.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/bb.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/omp_types.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/orca.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_types.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/paseo.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/commands.py +24 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/compose.py +45 -11
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/design.py +18 -6
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/features.py +2 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/fit.py +90 -18
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/lookahead.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/outcome.py +21 -8
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/pricing.py +22 -3
- loopmath-0.2.8/src/loopmath/belief/rules.py +139 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/state.py +106 -58
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/cli_registry.py +18 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_common.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_swimlanes.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/base.py +1 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/prices.toml +12 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/benchmarks.py +4 -4
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/commands.py +8 -6
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/share/commands.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/share/export.py +6 -6
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/share/import_.py +16 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/doctor.py +7 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/install.py +9 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/reference.md +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/commands.py +71 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/config.py +116 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/finish.py +219 -23
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/fitjob.py +2 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/home.py +15 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/record.py +18 -5
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/report.py +12 -3
- loopmath-0.2.8/src/loopmath/store/rule_change.py +137 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/builder.css +0 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/builder.js +50 -13
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/estimates.js +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/results.js +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/runs.js +23 -6
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/viz.js +2 -2
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/posterior.py +24 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/runs.py +47 -11
- {loopmath-0.2.6 → loopmath-0.2.8/src/loopmath.egg-info}/PKG-INFO +3 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath.egg-info/SOURCES.txt +6 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_atrium.py +8 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_bb.py +14 -9
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_omp.py +8 -3
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_orca.py +6 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_otel_genai.py +20 -15
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_paseo.py +6 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_help_lists.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_price.py +5 -5
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_taskmodel_features.py +1 -1
- loopmath-0.2.8/tests/test_v027_p3.py +106 -0
- loopmath-0.2.8/tests/test_v027_small_fixes.py +181 -0
- loopmath-0.2.8/tests/test_v028_sol_6_1.py +80 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/LICENSE +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/setup.cfg +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/spec/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/spec/ocp-v0.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/spec/ocp_conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/__main__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapter_host.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/base.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/bb_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/omp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/omp_facts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/omp_links.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/omp_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/opencode.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/opencode_store.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_usage.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/pi.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/pi_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/adapters/registry.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/block.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/check_pymc.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/forest.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/gaussian.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/belief/priors.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/builder/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/builder/cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/builder/context.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/builder/predict.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/builder/server.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/cli_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/cli_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/arms.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/cli_e0.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/estimate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/estimate_rework.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/figures.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/io.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/e0/verb.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/explain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_assembly.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_bayes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_masks.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_predict.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_pricing.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/fit_transfer.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/gaps.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/gitwalk.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/grade.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/grade_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/artifact_git.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/artifact_join.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/artifact_kinds.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/bashparse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/bashread_ops.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/bashwrite_ops.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/bashwrites.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/cache.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/codexio.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/codexio_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/codexio_scan.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/dataset.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/dataset_build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/dataset_core.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/dataset_edges.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/dataset_nodes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/extract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_costcurve.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_css.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_data.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_force.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/html_render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_pricing.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_node.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_records.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_result.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_session.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_report.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labeler_score.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/labels.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/launch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/launch_origin.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/ocp_emit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/ocp_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/runfile.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/runfile_export.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/runfile_finish.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/scan.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/schema.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/graph/token_completeness.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/claude_code.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/claude_code_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/codex.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/codex_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp_analysis.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp_artifact.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp_common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp_convert.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ingest/ocp_projection.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/clip.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/codex_window.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/costs.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/match.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/settle.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/logmatch/tariff.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/canonical.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/contractv3.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/emit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/graph_contract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/membership.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/migrate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/ocp/version.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/onboard/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/onboard/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/onboard/history.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/onboard/label.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/onboard/usual.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/output.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/pool.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/price.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/price_validation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/benchmarks.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/README.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/manifest.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/e0.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/history.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/lanes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/ocpdoc.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/reduce.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/registry.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/rq1.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/show.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/sweep.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/priors/validate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/backlog.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/curve.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/engine.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/gain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/message.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/search.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/stats.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/recommend/storeread.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/eligibility.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/terminal.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/terminal_exclusions.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/terminal_format.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/report/terminal_read.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/research_defaults.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/research_paths.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/scoring.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/scoring_comparisons.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/scoring_predictive.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/share/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/budget.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/ids.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/lock.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/run_names.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/runs.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/status.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/store/verify.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface_bootstrap.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface_estimation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface_frame.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface_presentation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/surface_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/taskmodel.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/types.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/README.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/base.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/builder.html +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/common.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/estimates.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/graph.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/graph.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/plans.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/plans.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/results.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/assets/viz.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/views/plans.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/candidates.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/solo.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/catalog/swarm.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/diff.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/format.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/graphview.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/ids.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/infer.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/models.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath/workflows/shapes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath.egg-info/dependency_links.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath.egg-info/entry_points.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath.egg-info/requires.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/src/loopmath.egg-info/top_level.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_opencode.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_opencode_output_tokens.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapter_services.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapters.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_adapters_pi.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_analyze_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_arms.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_main_module.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_plan_hidden.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_prices.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_cli_startup.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_color_proof.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_duplicate_guards.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_estimate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_explain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_fit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_gaps.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_gitwalk.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_grade.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_artifact_recording.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_bashwrites.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_cache.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_codexio.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_dataset.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_extract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_glue.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_labeler_evidence_matrix.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_launch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_runfile.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_graph_schema.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_home_guard.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ingest.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ingest_claude_code.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ingest_codex.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ingest_ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_io.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_not_run_summary.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ocp_conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ocp_golden.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ocp_open_enums.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_ocp_versioning.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_output_html_path.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_packaging_metadata.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_packaging_smoke.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_pool.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_preview_script.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_release_paths.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_report_eligibility.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_research_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_runfile_privacy_n1b.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_scoring.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_split_rebindings.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_starting_prior.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_surface.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_terminal.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_types_control.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_types_piece_prediction.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.8}/tests/test_v01_scaffold.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopmath
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs
|
|
5
5
|
Author: QKV Labs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -118,7 +118,7 @@ loopmath run start --rec REC --choice goal --base-commit SHA --json # the h
|
|
|
118
118
|
loopmath run record --run RUN --session UUID --verified tests=pass --json # costs from the logs, the outcome, the receipt
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
With `--run`, a session that started before the run counts from the run's start; a Codex session counts the usage it logged inside the window: each reading's increase at the reading's time, a reading on either edge included. Give `--since TS` and `--until TS` when the task's work began or ended at another time; `--since` before `run start` moves the run's start back. When no commit is found after the base commit, the record says where it looked; pass `--commit SHA`. `loopmath run sessions` lists the recent Claude Code and Codex sessions in this folder with their ids, for `--session ID`. Outside a Claude Code tool shell, `--session self` uses the one Claude Code session in this folder active in the last 10 minutes.
|
|
121
|
+
With `--run`, a session that started before the run counts from the run's start; a Codex session counts the usage it logged inside the window: each reading's increase at the reading's time, a reading on either edge included. A sub-agent or a Codex child thread counts in full when it started inside the window, and not at all when it started before it. A Codex reading lower than the one before it (a compaction reset) counts from zero. A Codex session that cannot be cut to the window counts whole, with a note that says why: a reading with no time, or increases inside the window that add up to less than zero. Give `--since TS` and `--until TS` when the task's work began or ended at another time; `--since` before `run start` moves the run's start back. When no commit is found after the base commit, the record says where it looked; pass `--commit SHA`. `loopmath run sessions` lists the recent Claude Code and Codex sessions in this folder with their ids, for `--session ID`. Outside a Claude Code tool shell, `--session self` uses the one Claude Code session in this folder active in the last 10 minutes.
|
|
122
122
|
|
|
123
123
|
The same loop step by step, for a tool of your own:
|
|
124
124
|
|
|
@@ -151,7 +151,7 @@ The usual workflow is the one most of your habit runs used: the runs onboard bro
|
|
|
151
151
|
|
|
152
152
|
A pair runs the goal workflow and an exploration pick from the same base commit, in separate worktrees. `loopmath run start --rec REC --choice pair` starts both, on a new slate. To start them one at a time, save the recommendation (`recommend ... --json > rec.json`) and start each run from it, so they share one task: `loopmath run start --task-file rec.json --config CFG --source alternative --new-slate` for the goal, then the same with the pick's configuration, `--source exploration` and `--slate SLT`. A blinded referee then picks the better change, which you record with `loopmath outcome --slate SLT --prefer RUN --judge referee --blinded`. `loopmath runs --slate SLT` says which run the referee preferred.
|
|
153
153
|
|
|
154
|
-
A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE` (`loopmath config get` lists every key, set or not,
|
|
154
|
+
A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A run that `onboard` brought in from your logs, or that `run import` stored, with no tests verdict but a review or accepted verdict, is judged by the review rule instead: a success when its latest review or accepted verdict is accept, approve or pass. `loopmath runs` lists other finished runs such a verdict could settle, with the command that moves them to the review rule, `loopmath outcome --rule review RUN...`. `loopmath outcome --undo-rule RUN...` undoes that, restoring each run's rule as it was; naming that rule (`tests` when it had none) does too, unless the run's own rule was also named review. A review verdict on a reviewer's last attempt then counts twice: as the review gate's pass, and as the run's success. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE` (`loopmath config get` lists every key, set or not, `config set` refuses a key loopmath does not read, and `loopmath config unset KEY` returns a key to its default), and the spending cap with `loopmath budget --usd X --period month`. Spend counts your runs and onboard's labelling calls; runs onboard brought in from your logs are shown apart and not counted. A run you record with `--source habit` counts.
|
|
155
155
|
|
|
156
156
|
Runs recorded by another tool that writes OCP v0.3, such as an experiment runner, come in with `loopmath run import DIR --finish`: several files or a directory, with one refit at the end (`--no-fit` skips it). It says how many runs were new and how many were already in the store; importing finished runs into a store with no fit starts a fit. Runs in your store always count as yours, whatever source their documents name. `loopmath fit --without SOURCE` leaves out one shipped prior source, and `loopmath status` names the options of the current fit.
|
|
157
157
|
|
|
@@ -80,7 +80,7 @@ loopmath run start --rec REC --choice goal --base-commit SHA --json # the h
|
|
|
80
80
|
loopmath run record --run RUN --session UUID --verified tests=pass --json # costs from the logs, the outcome, the receipt
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
With `--run`, a session that started before the run counts from the run's start; a Codex session counts the usage it logged inside the window: each reading's increase at the reading's time, a reading on either edge included. Give `--since TS` and `--until TS` when the task's work began or ended at another time; `--since` before `run start` moves the run's start back. When no commit is found after the base commit, the record says where it looked; pass `--commit SHA`. `loopmath run sessions` lists the recent Claude Code and Codex sessions in this folder with their ids, for `--session ID`. Outside a Claude Code tool shell, `--session self` uses the one Claude Code session in this folder active in the last 10 minutes.
|
|
83
|
+
With `--run`, a session that started before the run counts from the run's start; a Codex session counts the usage it logged inside the window: each reading's increase at the reading's time, a reading on either edge included. A sub-agent or a Codex child thread counts in full when it started inside the window, and not at all when it started before it. A Codex reading lower than the one before it (a compaction reset) counts from zero. A Codex session that cannot be cut to the window counts whole, with a note that says why: a reading with no time, or increases inside the window that add up to less than zero. Give `--since TS` and `--until TS` when the task's work began or ended at another time; `--since` before `run start` moves the run's start back. When no commit is found after the base commit, the record says where it looked; pass `--commit SHA`. `loopmath run sessions` lists the recent Claude Code and Codex sessions in this folder with their ids, for `--session ID`. Outside a Claude Code tool shell, `--session self` uses the one Claude Code session in this folder active in the last 10 minutes.
|
|
84
84
|
|
|
85
85
|
The same loop step by step, for a tool of your own:
|
|
86
86
|
|
|
@@ -113,7 +113,7 @@ The usual workflow is the one most of your habit runs used: the runs onboard bro
|
|
|
113
113
|
|
|
114
114
|
A pair runs the goal workflow and an exploration pick from the same base commit, in separate worktrees. `loopmath run start --rec REC --choice pair` starts both, on a new slate. To start them one at a time, save the recommendation (`recommend ... --json > rec.json`) and start each run from it, so they share one task: `loopmath run start --task-file rec.json --config CFG --source alternative --new-slate` for the goal, then the same with the pick's configuration, `--source exploration` and `--slate SLT`. A blinded referee then picks the better change, which you record with `loopmath outcome --slate SLT --prefer RUN --judge referee --blinded`. `loopmath runs --slate SLT` says which run the referee preferred.
|
|
115
115
|
|
|
116
|
-
A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE` (`loopmath config get` lists every key, set or not,
|
|
116
|
+
A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A run that `onboard` brought in from your logs, or that `run import` stored, with no tests verdict but a review or accepted verdict, is judged by the review rule instead: a success when its latest review or accepted verdict is accept, approve or pass. `loopmath runs` lists other finished runs such a verdict could settle, with the command that moves them to the review rule, `loopmath outcome --rule review RUN...`. `loopmath outcome --undo-rule RUN...` undoes that, restoring each run's rule as it was; naming that rule (`tests` when it had none) does too, unless the run's own rule was also named review. A review verdict on a reviewer's last attempt then counts twice: as the review gate's pass, and as the run's success. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE` (`loopmath config get` lists every key, set or not, `config set` refuses a key loopmath does not read, and `loopmath config unset KEY` returns a key to its default), and the spending cap with `loopmath budget --usd X --period month`. Spend counts your runs and onboard's labelling calls; runs onboard brought in from your logs are shown apart and not counted. A run you record with `--source habit` counts.
|
|
117
117
|
|
|
118
118
|
Runs recorded by another tool that writes OCP v0.3, such as an experiment runner, come in with `loopmath run import DIR --finish`: several files or a directory, with one refit at the end (`--no-fit` skips it). It says how many runs were new and how many were already in the store; importing finished runs into a store with no fit starts a fit. Runs in your store always count as yours, whatever source their documents name. `loopmath fit --without SOURCE` leaves out one shipped prior source, and `loopmath status` names the options of the current fit.
|
|
119
119
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "loopmath"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -5,7 +5,7 @@ typed subagent-stop events whose subagent id may be empty. The stored facts are
|
|
|
5
5
|
``verified``, but runtime pane ids are not stable OCP task-node ids and the
|
|
6
6
|
closed OCP edge vocabulary has no neutral message or runtime-correlation edge.
|
|
7
7
|
This adapter therefore preserves them as typed metadata-only events under the
|
|
8
|
-
documented ``dev.
|
|
8
|
+
documented ``dev.loopmath.adapter.atrium`` extension and emits no core graph edge. It
|
|
9
9
|
does not infer a spawn relation from a subagent-stop record or infer acceptance
|
|
10
10
|
from segment closure, session end, or subagent stop.
|
|
11
11
|
|
|
@@ -35,7 +35,7 @@ from .registry import register
|
|
|
35
35
|
|
|
36
36
|
_DEFAULT_ROOT = Path.home() / ".atrium"
|
|
37
37
|
_STORE_NAMES = ("tasks.db", "timeline.db")
|
|
38
|
-
_NAMESPACE = "dev.
|
|
38
|
+
_NAMESPACE = "dev.loopmath.adapter.atrium"
|
|
39
39
|
_TIMELINE_KINDS = ("agent-message", "session-end", "subagent-stop")
|
|
40
40
|
_DIAGNOSTIC_REASONS = (
|
|
41
41
|
"timeline_kind_unsupported",
|
|
@@ -10,7 +10,7 @@ from typing import Any, Mapping
|
|
|
10
10
|
from .base import Usage
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
_EXT_NAMESPACE = "dev.
|
|
13
|
+
_EXT_NAMESPACE = "dev.loopmath.adapter.omp"
|
|
14
14
|
_KNOWN_MESSAGE_ROLES = {"assistant", "system", "toolResult", "user"}
|
|
15
15
|
_TOKEN_FIELDS = {
|
|
16
16
|
"input": "input_tokens",
|
|
@@ -39,7 +39,7 @@ from .registry import register
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
_DEFAULT_STORE = Path.home() / "Library" / "Application Support" / "orca" / "orchestration.db"
|
|
42
|
-
_NAMESPACE = "dev.
|
|
42
|
+
_NAMESPACE = "dev.loopmath.adapter.orca"
|
|
43
43
|
_MESSAGE_TYPES = ("decision_gate", "escalation", "merge_ready", "worker_done")
|
|
44
44
|
_DIAGNOSTIC_REASONS = (
|
|
45
45
|
"start_options_missing",
|
|
@@ -13,7 +13,7 @@ from typing import Any, Iterable, Mapping
|
|
|
13
13
|
from urllib.parse import quote
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
EXT = "dev.
|
|
16
|
+
EXT = "dev.loopmath.adapter.otel-genai"
|
|
17
17
|
_TRACE_ID = re.compile(r"[0-9a-fA-F]{32}")
|
|
18
18
|
_SPAN_ID = re.compile(r"[0-9a-fA-F]{16}")
|
|
19
19
|
_RFC3339 = re.compile(
|
|
@@ -38,7 +38,7 @@ from .registry import register
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
_DEFAULT_STORE = Path.home() / ".paseo" / "agents"
|
|
41
|
-
_NAMESPACE = "dev.
|
|
41
|
+
_NAMESPACE = "dev.loopmath.adapter.paseo"
|
|
42
42
|
_TERMINAL = {"failed", "canceled", "settled_unverified"}
|
|
43
43
|
_STATUS = {
|
|
44
44
|
"working": "working",
|
|
@@ -74,6 +74,8 @@ def _summary(path: Path) -> dict:
|
|
|
74
74
|
"full": meta.get("full"),
|
|
75
75
|
"features": {k: v for k, v in (meta.get("features") or {}).items() if k != "declared"},
|
|
76
76
|
"horizons": meta.get("horizons") or {},
|
|
77
|
+
**({"receipts_rescored": meta["receipts_rescored"]} if meta.get("receipts_rescored") else {}),
|
|
78
|
+
**({"notes": meta["notes"]} if meta.get("notes") else {}),
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
|
|
@@ -85,6 +87,10 @@ def _print_summary(s: dict) -> None:
|
|
|
85
87
|
f = s["fit"]
|
|
86
88
|
n = f.get("n_runs") or {}
|
|
87
89
|
print(f"fit {f['id']} written to {s['path']} in {s.get('seconds')} s; recommend and posterior now read it")
|
|
90
|
+
rescored = s.get("receipts_rescored")
|
|
91
|
+
if rescored:
|
|
92
|
+
print(f"{rescored} receipt{'' if rescored == 1 else 's'} scored before 0.2.7 re-scored first: the run's rule is "
|
|
93
|
+
"read as the fit reads it (a note on each run)")
|
|
88
94
|
by_src = ", ".join(f"{k} {v}" for k, v in sorted(s["runs_by_source"].items(), key=lambda kv: -kv[1]))
|
|
89
95
|
labels = ", ".join(f"{k} {v}" for k, v in sorted((s.get("user_labels") or {}).items(), key=lambda kv: -kv[1]))
|
|
90
96
|
detail = "; ".join(x for x in (by_src, f"yours labelled {labels}" if labels else "") if x)
|
|
@@ -175,6 +181,9 @@ def fit(args: argparse.Namespace) -> int:
|
|
|
175
181
|
|
|
176
182
|
from .fit import FitBusy, NothingToFit, UnknownSource, fit as run_fit
|
|
177
183
|
|
|
184
|
+
notes = _config_notes(home)
|
|
185
|
+
for note in notes:
|
|
186
|
+
print(f"note: {note}", file=sys.stderr)
|
|
178
187
|
# --full: a failed compile makes pytensor print where it left the C code on stdout, which is for the result
|
|
179
188
|
quiet = contextlib.redirect_stdout(sys.stderr) if full else contextlib.nullcontext()
|
|
180
189
|
try:
|
|
@@ -192,8 +201,23 @@ def fit(args: argparse.Namespace) -> int:
|
|
|
192
201
|
raise
|
|
193
202
|
return _pymc_check_failed(home, exc)
|
|
194
203
|
summary = _summary(path)
|
|
204
|
+
if notes:
|
|
205
|
+
summary["notes"] = notes + summary.get("notes", []) # the fit printed its own on stderr
|
|
195
206
|
if as_json:
|
|
196
207
|
output.emit_json(SCHEMA, summary)
|
|
197
208
|
else:
|
|
198
209
|
_print_summary(summary)
|
|
199
210
|
return output.EXIT_OK
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _config_notes(home: Path) -> list[str]:
|
|
214
|
+
"""A config.toml the fit cannot read leaves it on the built-in values; say so, as `runs` does (0.2.7)."""
|
|
215
|
+
from ..store.config import READ_ERRORS, Config, read_error
|
|
216
|
+
|
|
217
|
+
path = Path(home) / "config.toml"
|
|
218
|
+
try:
|
|
219
|
+
Config.load(path)
|
|
220
|
+
except READ_ERRORS as exc:
|
|
221
|
+
return [f"{read_error(path, exc)}; outcomes use the built-in q, and the fit the built-in features and "
|
|
222
|
+
"benchmark weights"]
|
|
223
|
+
return []
|
|
@@ -18,6 +18,13 @@ simulated per draw (run noise and gate outcomes), and the interval is its 10th a
|
|
|
18
18
|
percentiles. Rounds, success and `ell` are parameter-draw quantities. Without a generator,
|
|
19
19
|
`compose` computes the expectations only (the mean path in state.py).
|
|
20
20
|
|
|
21
|
+
Tokens (0.2.8): one execution's dollars are `U = exp(eta_u + sigma_u z1)` and its price per token
|
|
22
|
+
`R = exp(eta_r + sigma_r z2)`, from the rate head; its tokens are `T = U / max(R, f)`, with `f` the model's
|
|
23
|
+
cache-read rate (its floor, none for a model without one). So `T f <= U` in every draw, and every shown pair of
|
|
24
|
+
dollars and tokens clears the floor: per draw, in the mean, at every percentile and summed over pieces. The two
|
|
25
|
+
normals are drawn in 0.2.7's order (dollars, then the second one), so the simulated dollars and the gates after
|
|
26
|
+
them are 0.2.7's. The expected tokens are `E[U] E[1 / max(R, f)]` (`inv_rate_mean`).
|
|
27
|
+
|
|
21
28
|
Per piece, `PieceDraws.exp_*` is the piece's expected full-run contribution, summed over
|
|
22
29
|
the rounds it runs, so pieces add up to the run. `cost_per_round()` gives the expected cost of
|
|
23
30
|
one execution: the per-execution prediction when the piece's cost does not depend on the round
|
|
@@ -28,8 +35,11 @@ from __future__ import annotations
|
|
|
28
35
|
|
|
29
36
|
from dataclasses import dataclass, field
|
|
30
37
|
|
|
38
|
+
import math
|
|
39
|
+
from collections.abc import Mapping
|
|
40
|
+
|
|
31
41
|
import numpy as np
|
|
32
|
-
from scipy.special import expit
|
|
42
|
+
from scipy.special import expit, log_ndtr
|
|
33
43
|
|
|
34
44
|
from .design import Structure
|
|
35
45
|
|
|
@@ -64,12 +74,33 @@ class RunDraws:
|
|
|
64
74
|
reach: dict[tuple[str, int], np.ndarray] = field(default_factory=dict) # (piece, k) -> P(piece runs in round k)
|
|
65
75
|
|
|
66
76
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
def inv_rate_mean(m, v, floor: float | None) -> np.ndarray:
|
|
78
|
+
"""`E[1 / max(R, f)]` for `log R ~ N(m, v)` and the floor `f` ($ per token; None or 0: no floor), per entry
|
|
79
|
+
of `m`. With `l = log f`: `exp(-m + v/2) Phi((m - l)/sqrt(v) - sqrt(v)) + exp(-l) Phi((l - m)/sqrt(v))`,
|
|
80
|
+
each term in logs (`log Phi`), so a far tail never multiplies an overflow by 0. With `v = 0` the rate is
|
|
81
|
+
certain: `1 / max(exp(m), f)`."""
|
|
82
|
+
m = np.asarray(m, float)
|
|
83
|
+
v = np.broadcast_to(np.asarray(v, float), m.shape)
|
|
84
|
+
if not floor or floor <= 0:
|
|
85
|
+
return np.exp(-m + 0.5 * v)
|
|
86
|
+
lf = math.log(floor)
|
|
87
|
+
certain = v <= 0
|
|
88
|
+
sd = np.sqrt(np.where(certain, 1.0, v))
|
|
89
|
+
spread = np.exp(-m + 0.5 * v + log_ndtr((m - lf) / sd - sd)) + np.exp(log_ndtr((lf - m) / sd) - lf)
|
|
90
|
+
return np.where(certain, np.exp(-np.maximum(m, lf)), spread)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def compose(st: Structure, cost_eta: dict, rate_eta: dict, gate_eta: dict, *, sigma_usd: float,
|
|
94
|
+
sigma_rate: float, floors: Mapping[str, float] | None = None, rate_var: Mapping | None = None,
|
|
95
|
+
rng: np.random.Generator | None) -> RunDraws:
|
|
96
|
+
"""Compose one configuration. `cost_eta[(piece, k)]`, `rate_eta[(piece, k)]` and `gate_eta[(gate index, k)]`
|
|
97
|
+
are draw arrays; `floors[piece]` is the piece model's floor in $ per token (absent or 0: none). The expected
|
|
98
|
+
tokens take `log R ~ N(rate_eta, rate_var[(piece, k)])`, `sigma_rate ** 2` when `rate_var` is None (the mean
|
|
99
|
+
path passes the rate rows' known parts and adds their unseen variance there). With `rng` None nothing is
|
|
100
|
+
simulated: the `sim_*` arrays stay zero."""
|
|
71
101
|
n = len(next(iter(cost_eta.values())))
|
|
72
|
-
half_u,
|
|
102
|
+
half_u, var_r = 0.5 * sigma_usd ** 2, sigma_rate ** 2
|
|
103
|
+
floors = floors or {}
|
|
73
104
|
exp_u, exp_t = np.zeros(n), np.zeros(n)
|
|
74
105
|
sim_u, sim_t = np.zeros(n), np.zeros(n)
|
|
75
106
|
rounds = np.ones(n)
|
|
@@ -79,7 +110,7 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
|
|
|
79
110
|
pieces[p] = PieceDraws(np.zeros(n), np.zeros(n), np.zeros(n), np.zeros(n), np.ones(n), sim_runs=np.zeros(n))
|
|
80
111
|
if st.piece_loop.get(p) is not None:
|
|
81
112
|
pieces[p].round_free = all(
|
|
82
|
-
np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(
|
|
113
|
+
np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(rate_eta[(p, k)], rate_eta[(p, 1)])
|
|
83
114
|
for k in range(2, st.k_max + 1))
|
|
84
115
|
gate_after = {g.after: gi for gi, g in enumerate(st.gates)}
|
|
85
116
|
for piece, gi in gate_after.items():
|
|
@@ -88,17 +119,20 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
|
|
|
88
119
|
|
|
89
120
|
def run_piece(piece: str, k: int, weight: np.ndarray, alive: np.ndarray):
|
|
90
121
|
w = st.widths[piece]
|
|
91
|
-
eu,
|
|
122
|
+
eu, er = cost_eta[(piece, k)], rate_eta[(piece, k)]
|
|
123
|
+
f = float(floors.get(piece) or 0.0)
|
|
92
124
|
cu = w * np.exp(eu + half_u)
|
|
93
|
-
ct =
|
|
125
|
+
ct = cu * inv_rate_mean(er, var_r if rate_var is None else rate_var[(piece, k)], f)
|
|
94
126
|
pd = pieces[piece]
|
|
95
127
|
pd.exp_usd += weight * cu
|
|
96
128
|
pd.exp_tokens += weight * ct
|
|
97
129
|
su = np.zeros(n)
|
|
98
130
|
stt = np.zeros(n)
|
|
99
131
|
for _ in range(w if rng is not None else 0):
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
u = np.exp(eu + sigma_usd * rng.standard_normal(n))
|
|
133
|
+
r = np.exp(er + sigma_rate * rng.standard_normal(n))
|
|
134
|
+
su += u
|
|
135
|
+
stt += u / (np.maximum(r, f) if f > 0 else r)
|
|
102
136
|
pd.sim_usd += alive * su
|
|
103
137
|
pd.sim_tokens += alive * stt
|
|
104
138
|
pd.sim_runs += alive
|
|
@@ -30,8 +30,12 @@ from .pricing import price_term
|
|
|
30
30
|
Term = tuple[str, "str | None", float]
|
|
31
31
|
|
|
32
32
|
# Node names of this design (spec 04 section 4). 2 (loopmath 0.2): shape keys, psrc nodes and role groups.
|
|
33
|
-
# 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon.
|
|
34
|
-
|
|
33
|
+
# 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon. 4 (0.2.8): a `rate` head on the price
|
|
34
|
+
# per token replaces the tokens head. A fit of another version is not read.
|
|
35
|
+
DESIGN_VERSION = 4
|
|
36
|
+
# The version the seed key hashes (spec 04 section 3). It stays 3 from 0.2.8: the same data seeds the same
|
|
37
|
+
# draws as in 0.2.7, so every number that is not a token count is 0.2.7's.
|
|
38
|
+
SEED_VERSION = 3
|
|
35
39
|
HEURISTIC_COST_WEIGHT = 0.7 # spec 04 section 2 and D27: allocated (heuristic) cost rows
|
|
36
40
|
USER_SOURCES = ("live", "backlog", "history", "user", "orchestrator", "onboard", "habit", "")
|
|
37
41
|
HORIZON_NODES = ("horizon:timebox", "horizon:log2h") # fixed terms of a timeboxed run (spec 04 section 1)
|
|
@@ -720,6 +724,14 @@ def _check_name(a: dict, node: dict) -> str | None:
|
|
|
720
724
|
return str(gate.get("rule") or node.get("id") or a.get("node") or a.get("harness") or "check")
|
|
721
725
|
|
|
722
726
|
|
|
727
|
+
def run_evidence(doc: dict, rule: AcceptanceRule, *, now: datetime,
|
|
728
|
+
q_by_tier: dict[str, float] | None = None) -> Evidence:
|
|
729
|
+
"""The run's outcome as the fit reads it: `rules.evidence` under `rule` (None `q_by_tier`: the built-in q)."""
|
|
730
|
+
from . import rules
|
|
731
|
+
|
|
732
|
+
return rules.evidence(doc, rule, now=now, q_by_tier=q_by_tier)
|
|
733
|
+
|
|
734
|
+
|
|
723
735
|
def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule | None = None,
|
|
724
736
|
origin: str | None = None, q_by_tier: dict[str, float] | None = None) -> ParsedRun:
|
|
725
737
|
"""Everything the fit needs from one finished OCP v0.3 run document. Raises Unusable.
|
|
@@ -727,8 +739,6 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
|
|
|
727
739
|
`origin` is where the fit found the document (`data_source`); None reads its label. `q_by_tier` is
|
|
728
740
|
`outcome.q.<tier>` from config.toml, passed to `outcome_evidence` (None: the built-in q).
|
|
729
741
|
"""
|
|
730
|
-
from .outcome import outcome_evidence
|
|
731
|
-
|
|
732
742
|
run = doc.get("run") or {}
|
|
733
743
|
cfg = run.get("configuration")
|
|
734
744
|
if not isinstance(cfg, dict):
|
|
@@ -786,8 +796,10 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
|
|
|
786
796
|
streams))
|
|
787
797
|
|
|
788
798
|
gates = _gate_observations(doc, st, by_piece_round)
|
|
789
|
-
|
|
790
|
-
|
|
799
|
+
from .rules import resolve_rule
|
|
800
|
+
|
|
801
|
+
use_rule = rule or resolve_rule(doc) # one resolver for every reader (0.2.7); raises on a malformed score
|
|
802
|
+
evidence = run_evidence(doc, use_rule, now=now or datetime.now().astimezone(), q_by_tier=q_by_tier)
|
|
791
803
|
score_meta: dict[str, dict] = {}
|
|
792
804
|
for s in run.get("signals") or []:
|
|
793
805
|
if s.get("kind") == "score" and isinstance(s.get("value"), (int, float)) and not isinstance(s.get("value"), bool):
|
|
@@ -35,6 +35,8 @@ LN2 = math.log(2.0)
|
|
|
35
35
|
# cost and tokens scale with an elasticity of 1 +- 0.5; logit and score heads start flat.
|
|
36
36
|
HORIZON_PRIOR = {
|
|
37
37
|
"cost": {"horizon:log2h": (LN2, 0.35), "horizon:timebox": (0.0, 1.5)},
|
|
38
|
+
# the price per token (0.2.8): a longer horizon buys more tokens, not dearer ones
|
|
39
|
+
"rate": {"horizon:log2h": (0.0, 0.35), "horizon:timebox": (0.0, 1.5)},
|
|
38
40
|
"logit": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
|
|
39
41
|
"score": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
|
|
40
42
|
}
|
|
@@ -31,12 +31,12 @@ from scipy import sparse
|
|
|
31
31
|
from ..taskmodel import FeatureConfigError, FeatureSet
|
|
32
32
|
from . import design as design_rows
|
|
33
33
|
from . import priors as prior_data
|
|
34
|
-
from .design import (DESIGN_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row,
|
|
35
|
-
task_features, task_terms)
|
|
34
|
+
from .design import (DESIGN_VERSION, SEED_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row,
|
|
35
|
+
structure, task_features, task_terms)
|
|
36
36
|
from .features import FeatureTally, add_horizon, drop_features, horizon_nodes, horizon_specs
|
|
37
37
|
from .block import leaf_split
|
|
38
38
|
from .forest import Forest, default_scale, fixed_sd, scale_group
|
|
39
|
-
from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term
|
|
39
|
+
from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term, rate_floors
|
|
40
40
|
from .gaussian import N_DRAWS, Factors, GaussianHead, HeadFit, LogisticHead, Prior
|
|
41
41
|
from .state import SCORE_CLAMP, inverse_transform, transform # noqa: F401 (re-exported)
|
|
42
42
|
|
|
@@ -44,8 +44,12 @@ FIT_LOCK = ".fit.lock"
|
|
|
44
44
|
KEEP_FITS = 5 # The newest fits kept, besides the ones a receipt or stored recommendation names
|
|
45
45
|
TASK_LEVELS = ("org", "type", "repo", "subtype", "task")
|
|
46
46
|
MIN_SCORE_RUNS = 3 # spec 04 section 2: a score head needs at least 3 measured runs
|
|
47
|
-
|
|
47
|
+
# `tokens` (log tokens) is 0.2.7's head: from 0.2.8 its rows are built for the seed key and the node index
|
|
48
|
+
# only, never fitted. `rate` (log usd - log tokens, the price per token) is fitted in its place (spec 04 section 2).
|
|
49
|
+
HEAD_KIND = {"cost": ("gaussian", "cost"), "tokens": ("gaussian", "cost"), "rate": ("gaussian", "cost"),
|
|
48
50
|
"gate": ("logistic", "logit"), "success": ("logistic", "logit")}
|
|
51
|
+
KEY_ONLY = ("tokens",) # heads whose rows the seed key and the node index read, and no fit
|
|
52
|
+
NOT_KEYED = ("rate",) # heads the seed key leaves out, so 0.2.8 seeds every draw as 0.2.7 did
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
class FitBusy(RuntimeError):
|
|
@@ -146,7 +150,7 @@ def _read_config(home: Path) -> dict:
|
|
|
146
150
|
path = Path(home) / "config.toml"
|
|
147
151
|
try:
|
|
148
152
|
return tomllib.loads(path.read_text(encoding="utf-8"))
|
|
149
|
-
except (OSError,
|
|
153
|
+
except (OSError, ValueError): # unreadable, not TOML, or not UTF-8: the built-in values (`fit` says so)
|
|
150
154
|
return {}
|
|
151
155
|
|
|
152
156
|
|
|
@@ -165,6 +169,8 @@ class HeadRows:
|
|
|
165
169
|
types: list[str] = field(default_factory=list)
|
|
166
170
|
not_repriced: int = 0 # cost rows on recorded dollars, not the current tariff
|
|
167
171
|
prices: dict = field(default_factory=dict) # cost head: the fit's price offsets (pricing.fit_offsets)
|
|
172
|
+
# success head, with a configured q only: each row's w under the built-in q, which the seed hashes (0.2.7)
|
|
173
|
+
seed_w: list[float] = field(default_factory=list)
|
|
168
174
|
|
|
169
175
|
def add(self, terms, y, w, run, source, ttype):
|
|
170
176
|
self.rows.append(terms)
|
|
@@ -206,6 +212,7 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
206
212
|
features = features or FeatureSet()
|
|
207
213
|
tally = FeatureTally(features)
|
|
208
214
|
heads = {name: HeadRows(name, *HEAD_KIND[name]) for name in HEAD_KIND}
|
|
215
|
+
configured_q = custom_q(q_by_tier) is not None
|
|
209
216
|
score_raw: dict[str, list] = defaultdict(list)
|
|
210
217
|
score_meta: dict[str, Counter] = defaultdict(Counter)
|
|
211
218
|
score_info: dict[str, dict] = {}
|
|
@@ -270,6 +277,9 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
270
277
|
acc[i] += v
|
|
271
278
|
if a.tokens:
|
|
272
279
|
heads["tokens"].add(terms, math.log(a.tokens), a.weight, pr.run_id, pr.source, ttype)
|
|
280
|
+
if a.usd and a.tokens:
|
|
281
|
+
heads["rate"].add(terms, math.log(a.usd) - math.log(a.tokens), a.weight, pr.run_id, pr.source,
|
|
282
|
+
ttype)
|
|
273
283
|
for gi, k, passed in pr.gates:
|
|
274
284
|
heads["gate"].add(gate_row(pr.task, pr.source, st, st.gates[gi], k, features), 1.0 if passed else 0.0,
|
|
275
285
|
1.0, pr.run_id, pr.source, ttype)
|
|
@@ -277,6 +287,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
277
287
|
ev = pr.evidence
|
|
278
288
|
if ev is not None and ev.z is not None:
|
|
279
289
|
heads["success"].add(row, float(ev.z), float(ev.q), pr.run_id, pr.source, ttype)
|
|
290
|
+
if configured_q: # z and the tier do not depend on q, so this read differs from `ev` in q only
|
|
291
|
+
heads["success"].seed_w.append(float(design_rows.run_evidence(doc, pr.rule, now=now).q))
|
|
280
292
|
for name, value in (ev.scores if ev else {}).items():
|
|
281
293
|
meta = pr.score_meta.get(name) or {}
|
|
282
294
|
score_meta[name][(meta.get("scale") or "linear", meta.get("better") or "higher", meta.get("unit"))] += 1
|
|
@@ -300,6 +312,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
300
312
|
for hr in heads.values():
|
|
301
313
|
hr.rows = add_horizon(drop_features(hr.rows, tally.nodes), hr.runs, tally.run_horizon, coding)
|
|
302
314
|
add_prices(heads["cost"], {m: len(r) for m, r in model_runs.items()}, model_streams)
|
|
315
|
+
heads["rate"].prices = heads["cost"].prices # the price per token carries the same offset (spec 04 section 2)
|
|
316
|
+
_price_rows(heads["rate"])
|
|
303
317
|
return (heads, score_info, dict(dropped), dict(runs_by_source), {c: dict(n) for c, n in config_support.items()},
|
|
304
318
|
dict(checks), dict(overlap), dict(labels), tally)
|
|
305
319
|
|
|
@@ -308,7 +322,12 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
|
|
|
308
322
|
"""The price offset (spec 04 section 2): the fit's offsets from its runs and token mix under the current price
|
|
309
323
|
table, and each cost row with its model's `price:offset` term (`pricing.price_term`, as at prediction)."""
|
|
310
324
|
hr.prices = fit_offsets(runs, streams, design_rows._price_table())
|
|
311
|
-
|
|
325
|
+
_price_rows(hr)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _price_rows(hr: HeadRows) -> None:
|
|
329
|
+
"""Each row with its model's `price:offset` term under `hr.prices`."""
|
|
330
|
+
offsets = hr.prices.get("models")
|
|
312
331
|
if not offsets:
|
|
313
332
|
return
|
|
314
333
|
rows = []
|
|
@@ -320,10 +339,11 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
|
|
|
320
339
|
|
|
321
340
|
|
|
322
341
|
def price_spec(hr: HeadRows) -> list:
|
|
323
|
-
"""The prior that holds `price:offset` at 1, on the cost
|
|
342
|
+
"""The prior that holds `price:offset` at 1, on the cost and rate heads of a fit with offsets, --no-prior
|
|
343
|
+
included."""
|
|
324
344
|
if not hr.prices.get("models"):
|
|
325
345
|
return []
|
|
326
|
-
return [prior_data.FactorSpec(
|
|
346
|
+
return [prior_data.FactorSpec(hr.name, [(PRICE_NODE, None, 1.0)], 1.0, PRICE_PRIOR_SD ** 2,
|
|
327
347
|
f"{PRICE_NODE} held at 1, prior N(1, {PRICE_PRIOR_SD}^2)")]
|
|
328
348
|
|
|
329
349
|
|
|
@@ -396,24 +416,41 @@ def _support(hr: HeadRows, X: sparse.csr_matrix, node_ids: list[str]) -> dict[st
|
|
|
396
416
|
return out
|
|
397
417
|
|
|
398
418
|
|
|
419
|
+
def custom_q(q_by_tier: dict[str, float] | None) -> dict[str, float] | None:
|
|
420
|
+
"""`outcome.q.<tier>` for every tier when it differs from the built-in q anywhere, else None."""
|
|
421
|
+
from .outcome import TIER_Q
|
|
422
|
+
|
|
423
|
+
if not q_by_tier:
|
|
424
|
+
return None
|
|
425
|
+
merged = {tier: float(q_by_tier.get(tier, q)) for tier, q in TIER_Q.items()}
|
|
426
|
+
return merged if merged != TIER_Q else None
|
|
427
|
+
|
|
428
|
+
|
|
399
429
|
def seed_for(fit_id: str, head: str) -> int:
|
|
400
430
|
"""A head's seed from the fit's `seed_key` (its fit id before 0.2.1)."""
|
|
401
431
|
return int.from_bytes(hashlib.sha256(f"{fit_id}/{head}".encode()).digest()[:4], "little")
|
|
402
432
|
|
|
403
433
|
|
|
404
434
|
def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> str:
|
|
405
|
-
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the
|
|
435
|
+
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the seed version, the settings that change the
|
|
406
436
|
posterior, the prior factors and every head's rows (terms, response, weight, run, source), each head's rows
|
|
407
|
-
sorted, so the same data and settings seed the same draws whatever the store's reading order.
|
|
437
|
+
sorted, so the same data and settings seed the same draws whatever the store's reading order.
|
|
438
|
+
|
|
439
|
+
A success row's weight is its q. With `outcome.q` configured, the key takes each row's q under the built-in
|
|
440
|
+
map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7).
|
|
441
|
+
|
|
442
|
+
From 0.2.8 the key reads 0.2.7's heads: the tokens rows, which no head fits any more, and not the rate rows,
|
|
443
|
+
with the seed version 0.2.7 hashed as its design version. The same data seeds the same draws as in 0.2.7."""
|
|
408
444
|
h = hashlib.sha256()
|
|
409
|
-
h.update(repr((
|
|
445
|
+
h.update(repr((SEED_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
|
|
410
446
|
sorted(settings.items()))).encode())
|
|
411
447
|
for spec in specs:
|
|
412
448
|
h.update(repr((spec.head, spec.terms, spec.mean, spec.var)).encode())
|
|
413
|
-
for name in sorted(heads_rows):
|
|
449
|
+
for name in sorted(n for n in heads_rows if n not in NOT_KEYED):
|
|
414
450
|
hr = heads_rows[name]
|
|
415
451
|
h.update(f"\n{name}:{len(hr.rows)}".encode())
|
|
416
|
-
|
|
452
|
+
weights = hr.seed_w or hr.w
|
|
453
|
+
for line in sorted(repr((t, y, w, r, src)) for t, y, w, r, src in zip(hr.rows, hr.y, weights, hr.runs, hr.sources)):
|
|
417
454
|
h.update(line.encode())
|
|
418
455
|
return h.hexdigest()
|
|
419
456
|
|
|
@@ -513,12 +550,38 @@ def point_latest(fits: Path, fit_id: str) -> None:
|
|
|
513
550
|
os.replace(tmp, fits / "latest")
|
|
514
551
|
|
|
515
552
|
|
|
553
|
+
def _settle_receipts(home: Path) -> tuple[list[str], str | None]:
|
|
554
|
+
"""Receipts `run finish` scored before 0.2.7 under a rule it read otherwise than the fit are re-scored before
|
|
555
|
+
every fit of the store, foreground or background (`store.finish.settle_rule_receipts`). A receipt that could
|
|
556
|
+
not be read, re-scored or written, or a run file that could not be written, is named in a note on stderr, and the
|
|
557
|
+
next fit does what is left. Returns the receipts re-scored and the note, if any."""
|
|
558
|
+
import sys
|
|
559
|
+
|
|
560
|
+
from ..store.finish import settle_rule_receipts
|
|
561
|
+
from ..store.home import Store
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
out = settle_rule_receipts(Store(home))
|
|
565
|
+
except Exception as exc: # the fit goes on; the receipts wait for the next one
|
|
566
|
+
note = f"receipts scored before 0.2.7 were not re-scored: {type(exc).__name__}: {exc}"[:300]
|
|
567
|
+
print(f"note: {note}", file=sys.stderr)
|
|
568
|
+
return [], note
|
|
569
|
+
failed = out["failed"]
|
|
570
|
+
if not failed:
|
|
571
|
+
return out["rescored"], None
|
|
572
|
+
shown = "; ".join(failed[:3]) + (f"; and {len(failed) - 3} more" if len(failed) > 3 else "")
|
|
573
|
+
note = f"receipts scored before 0.2.7 are not all settled yet; the next fit does what is left: {shown}"
|
|
574
|
+
print(f"note: {note}", file=sys.stderr)
|
|
575
|
+
return out["rescored"], note
|
|
576
|
+
|
|
577
|
+
|
|
516
578
|
def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), full: bool = False,
|
|
517
579
|
now: datetime | None = None, docs: Iterable[dict] | None = None, bundle_dir: Path | None = None,
|
|
518
580
|
benchmarks: Path | None = None, eb: bool = True, wait_s: float = 0.0) -> Path:
|
|
519
|
-
"""Build the belief and write `fits/<fit>/`; returns its path.
|
|
581
|
+
"""Build the belief and write `fits/<fit>/`; returns its path. A fit of the store's own runs (`docs` None)
|
|
582
|
+
first settles the receipts scored before 0.2.7 (`_settle_receipts`).
|
|
520
583
|
|
|
521
|
-
Raises FitBusy, or UnknownSource / NothingToFit before
|
|
584
|
+
Raises FitBusy, or UnknownSource / NothingToFit before any fit file is written.
|
|
522
585
|
"""
|
|
523
586
|
home = Path(home)
|
|
524
587
|
fits = home / "fits"
|
|
@@ -527,6 +590,7 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
527
590
|
without = tuple(without)
|
|
528
591
|
with fit_lock(home, wait_s):
|
|
529
592
|
remove_partials(home)
|
|
593
|
+
settled, settle_note = _settle_receipts(home) if docs is None else ([], None)
|
|
530
594
|
config = _read_config(home)
|
|
531
595
|
raw_weight = config.get("benchmark_prior_weight") # unset: each benchmark's own weight (spec 04 section 5)
|
|
532
596
|
weight = None if raw_weight is None else float(raw_weight)
|
|
@@ -572,9 +636,12 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
572
636
|
continue
|
|
573
637
|
nodes = horizon_nodes(hr.rows)
|
|
574
638
|
if nodes: # the horizon priors, --no-prior included (spec 04 section 1)
|
|
575
|
-
head_specs += horizon_specs(name, hr.kind, nodes)
|
|
576
|
-
if name
|
|
639
|
+
head_specs += horizon_specs(name, "rate" if name == "rate" else hr.kind, nodes)
|
|
640
|
+
if name in ("cost", "rate"): # the price offset (spec 04 section 2)
|
|
577
641
|
head_specs += price_spec(hr)
|
|
642
|
+
if name in KEY_ONLY: # 0.2.7's tokens head: its nodes enter the index where they did, unfitted
|
|
643
|
+
build_matrix(hr.rows, forest, [(n, p) for spec in head_specs for n, p, _ in spec.terms])
|
|
644
|
+
continue
|
|
578
645
|
fitted[name] = fit_head(hr, forest, head_specs, eb=eb)
|
|
579
646
|
fit_id = new_fit_id(fits, now)
|
|
580
647
|
partial = fits / f"{fit_id}.partial"
|
|
@@ -610,7 +677,10 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
610
677
|
_atomic_json(partial / "design.json", design)
|
|
611
678
|
meta = {
|
|
612
679
|
"fit": fit_id, "created_at": now.isoformat(timespec="seconds"), "code_version": _code_version(),
|
|
613
|
-
"seed_key": seed_key, "
|
|
680
|
+
"seed_key": seed_key, **({"outcome_q": custom_q(q_by_tier)} if custom_q(q_by_tier) else {}),
|
|
681
|
+
**({"receipts_rescored": len(settled)} if settled else {}),
|
|
682
|
+
**({"notes": [settle_note]} if settle_note else {}),
|
|
683
|
+
"timebox_effort": design_rows.TIMEBOX_EFFORT_COST,
|
|
614
684
|
"timebox_terms": list(design_rows.TIMEBOX_LEVELS),
|
|
615
685
|
"options": {"no_prior": no_prior, "without": list(without), "full": full, "eb": eb,
|
|
616
686
|
"benchmark_prior_weight": weight, "benchmark_weights": bench_weights},
|
|
@@ -636,6 +706,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
636
706
|
"features": {**tally.report, **({"error": features_error} if features_error else {})},
|
|
637
707
|
"horizons": tally.horizon_report(),
|
|
638
708
|
"price_offsets": heads_rows["cost"].prices,
|
|
709
|
+
# the cheapest rate of each priced model, $ per token: a draw's tokens never cost less (spec 04 section 3)
|
|
710
|
+
"rate_floors": rate_floors(design_rows._price_table()),
|
|
639
711
|
}
|
|
640
712
|
if full:
|
|
641
713
|
meta["full"] = _full_check(fitted)
|
|
@@ -394,7 +394,7 @@ def _condition_head(head: HeadState, terms: Sequence[tuple], tau2: float) -> Hea
|
|
|
394
394
|
def conditioned(state: FitState, task: Task, config: Configuration, rule: AcceptanceRule | None = None) -> FitState:
|
|
395
395
|
heads = dict(state.heads)
|
|
396
396
|
terms, n_obs = _explore_cost_obs(state, task, config)
|
|
397
|
-
for name in ("cost", "
|
|
397
|
+
for name in ("cost", "rate"):
|
|
398
398
|
heads[name] = _condition_head(state.heads[name], terms, state.heads[name].sigma ** 2 / n_obs)
|
|
399
399
|
run_terms = state.task_terms(task) + list(state._plan(config)["run"])
|
|
400
400
|
succ = state.heads["success"]
|