loopmath 0.2.6__tar.gz → 0.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loopmath-0.2.6/src/loopmath.egg-info → loopmath-0.2.7}/PKG-INFO +3 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/README.md +2 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/pyproject.toml +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/__init__.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/atrium.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/bb.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/omp_types.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/orca.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/otel_genai_types.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/paseo.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/commands.py +24 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/design.py +12 -4
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/fit.py +55 -6
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/outcome.py +21 -8
- loopmath-0.2.7/src/loopmath/belief/rules.py +139 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/cli_registry.py +18 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_common.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_swimlanes.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/share/commands.py +2 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/share/export.py +6 -6
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/share/import_.py +16 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/doctor.py +7 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/install.py +9 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/reference.md +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/commands.py +71 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/config.py +116 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/finish.py +219 -23
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/fitjob.py +2 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/home.py +15 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/record.py +18 -5
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/report.py +12 -3
- loopmath-0.2.7/src/loopmath/store/rule_change.py +137 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/builder.css +0 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/builder.js +50 -13
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/estimates.js +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/results.js +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/runs.js +23 -6
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/viz.js +2 -2
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/posterior.py +24 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/runs.py +47 -11
- {loopmath-0.2.6 → loopmath-0.2.7/src/loopmath.egg-info}/PKG-INFO +3 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath.egg-info/SOURCES.txt +5 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_atrium.py +8 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_bb.py +14 -9
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_omp.py +8 -3
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_orca.py +6 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_otel_genai.py +20 -15
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_paseo.py +6 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_help_lists.py +1 -1
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_taskmodel_features.py +1 -1
- loopmath-0.2.7/tests/test_v027_p3.py +106 -0
- loopmath-0.2.7/tests/test_v027_small_fixes.py +181 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/LICENSE +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/setup.cfg +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/spec/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/spec/ocp-v0.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/spec/ocp_conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/__main__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapter_host.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/base.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/bb_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/omp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/omp_facts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/omp_links.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/omp_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/opencode.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/opencode_store.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/otel_genai.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/otel_genai_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/otel_genai_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/otel_genai_usage.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/pi.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/pi_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/adapters/registry.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/block.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/check_pymc.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/compose.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/features.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/forest.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/gaussian.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/lookahead.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/pricing.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/priors.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/belief/state.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/builder/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/builder/cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/builder/context.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/builder/predict.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/builder/server.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/cli_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/cli_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/arms.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/cli_e0.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/estimate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/estimate_rework.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/figures.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/io.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/e0/verb.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/explain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_assembly.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_bayes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_masks.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_predict.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_pricing.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/fit_transfer.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/gaps.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/gitwalk.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/grade.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/grade_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/artifact_git.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/artifact_join.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/artifact_kinds.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/bashparse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/bashread_ops.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/bashwrite_ops.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/bashwrites.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/cache.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/codexio.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/codexio_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/codexio_scan.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/dataset.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/dataset_build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/dataset_core.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/dataset_edges.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/dataset_nodes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/extract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_costcurve.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_css.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_data.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_force.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/html_render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_parse.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_pricing.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_node.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_records.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_result.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_session.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_report.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labeler_score.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/labels.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/launch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/launch_origin.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/ocp_emit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/ocp_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/runfile.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/runfile_export.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/runfile_finish.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/scan.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/schema.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/graph/token_completeness.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/base.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/claude_code.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/claude_code_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/codex.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/codex_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp_analysis.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp_artifact.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp_common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp_convert.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ingest/ocp_projection.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/clip.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/codex_window.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/costs.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/match.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/settle.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/logmatch/tariff.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/canonical.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/contractv3.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/emit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/graph_contract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/membership.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/migrate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/ocp/version.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/onboard/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/onboard/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/onboard/history.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/onboard/label.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/onboard/usual.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/output.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/pool.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/price.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/price_validation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/prices.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/benchmarks.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/benchmarks.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/build.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/README.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/manifest.json +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/e0.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/history.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/lanes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/ocpdoc.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/reduce.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/registry.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/rq1.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/show.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/sweep.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/priors/validate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/backlog.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/curve.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/engine.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/gain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/message.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/search.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/stats.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/recommend/storeread.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/eligibility.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/terminal.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/terminal_exclusions.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/terminal_format.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/report/terminal_read.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/research_defaults.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/research_paths.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/scoring.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/scoring_comparisons.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/scoring_predictive.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/share/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/budget.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/ids.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/lock.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/run_names.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/runs.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/status.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/store/verify.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface_bootstrap.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface_estimation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface_frame.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface_presentation.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/surface_support.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/taskmodel.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/types.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/README.md +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/base.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/builder.html +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/common.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/estimates.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/graph.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/graph.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/plans.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/plans.js +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/results.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/assets/viz.css +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/common.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/views/plans.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/__init__.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/candidates.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/solo.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/catalog/swarm.toml +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/commands.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/diff.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/format.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/graphview.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/ids.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/infer.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/models.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath/workflows/shapes.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath.egg-info/dependency_links.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath.egg-info/entry_points.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath.egg-info/requires.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/src/loopmath.egg-info/top_level.txt +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_opencode.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_opencode_output_tokens.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapter_services.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapters.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_adapters_pi.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_analyze_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_arms.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_graph.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_main_module.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_plan_hidden.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_prices.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_cli_startup.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_color_proof.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_duplicate_guards.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_estimate.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_explain.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_fit.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_gaps.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_gitwalk.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_grade.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_artifact_recording.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_artifacts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_bashwrites.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_cache.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_codexio.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_dataset.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_extract.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_glue.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_labeler.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_labeler_evidence_matrix.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_launch.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_render.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_runfile.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_graph_schema.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_home_guard.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_html.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ingest.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ingest_claude_code.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ingest_codex.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ingest_ocp.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_io.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_not_run_summary.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ocp_conformance.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ocp_golden.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ocp_open_enums.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_ocp_versioning.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_output_html_path.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_packaging_metadata.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_packaging_smoke.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_pool.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_preview_script.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_price.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_receipts.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_release_paths.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_report_eligibility.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_research_cli.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_runfile_privacy_n1b.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_scoring.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_split_rebindings.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_starting_prior.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_surface.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_terminal.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_types_control.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/tests/test_types_piece_prediction.py +0 -0
- {loopmath-0.2.6 → loopmath-0.2.7}/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.7
|
|
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.7"
|
|
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 []
|
|
@@ -720,6 +720,14 @@ def _check_name(a: dict, node: dict) -> str | None:
|
|
|
720
720
|
return str(gate.get("rule") or node.get("id") or a.get("node") or a.get("harness") or "check")
|
|
721
721
|
|
|
722
722
|
|
|
723
|
+
def run_evidence(doc: dict, rule: AcceptanceRule, *, now: datetime,
|
|
724
|
+
q_by_tier: dict[str, float] | None = None) -> Evidence:
|
|
725
|
+
"""The run's outcome as the fit reads it: `rules.evidence` under `rule` (None `q_by_tier`: the built-in q)."""
|
|
726
|
+
from . import rules
|
|
727
|
+
|
|
728
|
+
return rules.evidence(doc, rule, now=now, q_by_tier=q_by_tier)
|
|
729
|
+
|
|
730
|
+
|
|
723
731
|
def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule | None = None,
|
|
724
732
|
origin: str | None = None, q_by_tier: dict[str, float] | None = None) -> ParsedRun:
|
|
725
733
|
"""Everything the fit needs from one finished OCP v0.3 run document. Raises Unusable.
|
|
@@ -727,8 +735,6 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
|
|
|
727
735
|
`origin` is where the fit found the document (`data_source`); None reads its label. `q_by_tier` is
|
|
728
736
|
`outcome.q.<tier>` from config.toml, passed to `outcome_evidence` (None: the built-in q).
|
|
729
737
|
"""
|
|
730
|
-
from .outcome import outcome_evidence
|
|
731
|
-
|
|
732
738
|
run = doc.get("run") or {}
|
|
733
739
|
cfg = run.get("configuration")
|
|
734
740
|
if not isinstance(cfg, dict):
|
|
@@ -786,8 +792,10 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
|
|
|
786
792
|
streams))
|
|
787
793
|
|
|
788
794
|
gates = _gate_observations(doc, st, by_piece_round)
|
|
789
|
-
|
|
790
|
-
|
|
795
|
+
from .rules import resolve_rule
|
|
796
|
+
|
|
797
|
+
use_rule = rule or resolve_rule(doc) # one resolver for every reader (0.2.7); raises on a malformed score
|
|
798
|
+
evidence = run_evidence(doc, use_rule, now=now or datetime.now().astimezone(), q_by_tier=q_by_tier)
|
|
791
799
|
score_meta: dict[str, dict] = {}
|
|
792
800
|
for s in run.get("signals") or []:
|
|
793
801
|
if s.get("kind") == "score" and isinstance(s.get("value"), (int, float)) and not isinstance(s.get("value"), bool):
|
|
@@ -146,7 +146,7 @@ def _read_config(home: Path) -> dict:
|
|
|
146
146
|
path = Path(home) / "config.toml"
|
|
147
147
|
try:
|
|
148
148
|
return tomllib.loads(path.read_text(encoding="utf-8"))
|
|
149
|
-
except (OSError,
|
|
149
|
+
except (OSError, ValueError): # unreadable, not TOML, or not UTF-8: the built-in values (`fit` says so)
|
|
150
150
|
return {}
|
|
151
151
|
|
|
152
152
|
|
|
@@ -165,6 +165,8 @@ class HeadRows:
|
|
|
165
165
|
types: list[str] = field(default_factory=list)
|
|
166
166
|
not_repriced: int = 0 # cost rows on recorded dollars, not the current tariff
|
|
167
167
|
prices: dict = field(default_factory=dict) # cost head: the fit's price offsets (pricing.fit_offsets)
|
|
168
|
+
# success head, with a configured q only: each row's w under the built-in q, which the seed hashes (0.2.7)
|
|
169
|
+
seed_w: list[float] = field(default_factory=list)
|
|
168
170
|
|
|
169
171
|
def add(self, terms, y, w, run, source, ttype):
|
|
170
172
|
self.rows.append(terms)
|
|
@@ -206,6 +208,7 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
206
208
|
features = features or FeatureSet()
|
|
207
209
|
tally = FeatureTally(features)
|
|
208
210
|
heads = {name: HeadRows(name, *HEAD_KIND[name]) for name in HEAD_KIND}
|
|
211
|
+
configured_q = custom_q(q_by_tier) is not None
|
|
209
212
|
score_raw: dict[str, list] = defaultdict(list)
|
|
210
213
|
score_meta: dict[str, Counter] = defaultdict(Counter)
|
|
211
214
|
score_info: dict[str, dict] = {}
|
|
@@ -277,6 +280,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
277
280
|
ev = pr.evidence
|
|
278
281
|
if ev is not None and ev.z is not None:
|
|
279
282
|
heads["success"].add(row, float(ev.z), float(ev.q), pr.run_id, pr.source, ttype)
|
|
283
|
+
if configured_q: # z and the tier do not depend on q, so this read differs from `ev` in q only
|
|
284
|
+
heads["success"].seed_w.append(float(design_rows.run_evidence(doc, pr.rule, now=now).q))
|
|
280
285
|
for name, value in (ev.scores if ev else {}).items():
|
|
281
286
|
meta = pr.score_meta.get(name) or {}
|
|
282
287
|
score_meta[name][(meta.get("scale") or "linear", meta.get("better") or "higher", meta.get("unit"))] += 1
|
|
@@ -396,6 +401,16 @@ def _support(hr: HeadRows, X: sparse.csr_matrix, node_ids: list[str]) -> dict[st
|
|
|
396
401
|
return out
|
|
397
402
|
|
|
398
403
|
|
|
404
|
+
def custom_q(q_by_tier: dict[str, float] | None) -> dict[str, float] | None:
|
|
405
|
+
"""`outcome.q.<tier>` for every tier when it differs from the built-in q anywhere, else None."""
|
|
406
|
+
from .outcome import TIER_Q
|
|
407
|
+
|
|
408
|
+
if not q_by_tier:
|
|
409
|
+
return None
|
|
410
|
+
merged = {tier: float(q_by_tier.get(tier, q)) for tier, q in TIER_Q.items()}
|
|
411
|
+
return merged if merged != TIER_Q else None
|
|
412
|
+
|
|
413
|
+
|
|
399
414
|
def seed_for(fit_id: str, head: str) -> int:
|
|
400
415
|
"""A head's seed from the fit's `seed_key` (its fit id before 0.2.1)."""
|
|
401
416
|
return int.from_bytes(hashlib.sha256(f"{fit_id}/{head}".encode()).digest()[:4], "little")
|
|
@@ -404,7 +419,10 @@ def seed_for(fit_id: str, head: str) -> int:
|
|
|
404
419
|
def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> str:
|
|
405
420
|
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the design version, the settings that change the
|
|
406
421
|
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.
|
|
422
|
+
sorted, so the same data and settings seed the same draws whatever the store's reading order.
|
|
423
|
+
|
|
424
|
+
A success row's weight is its q. With `outcome.q` configured, the key takes each row's q under the built-in
|
|
425
|
+
map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7)."""
|
|
408
426
|
h = hashlib.sha256()
|
|
409
427
|
h.update(repr((DESIGN_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
|
|
410
428
|
sorted(settings.items()))).encode())
|
|
@@ -413,7 +431,8 @@ def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> s
|
|
|
413
431
|
for name in sorted(heads_rows):
|
|
414
432
|
hr = heads_rows[name]
|
|
415
433
|
h.update(f"\n{name}:{len(hr.rows)}".encode())
|
|
416
|
-
|
|
434
|
+
weights = hr.seed_w or hr.w
|
|
435
|
+
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
436
|
h.update(line.encode())
|
|
418
437
|
return h.hexdigest()
|
|
419
438
|
|
|
@@ -513,12 +532,38 @@ def point_latest(fits: Path, fit_id: str) -> None:
|
|
|
513
532
|
os.replace(tmp, fits / "latest")
|
|
514
533
|
|
|
515
534
|
|
|
535
|
+
def _settle_receipts(home: Path) -> tuple[list[str], str | None]:
|
|
536
|
+
"""Receipts `run finish` scored before 0.2.7 under a rule it read otherwise than the fit are re-scored before
|
|
537
|
+
every fit of the store, foreground or background (`store.finish.settle_rule_receipts`). A receipt that could
|
|
538
|
+
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
|
|
539
|
+
next fit does what is left. Returns the receipts re-scored and the note, if any."""
|
|
540
|
+
import sys
|
|
541
|
+
|
|
542
|
+
from ..store.finish import settle_rule_receipts
|
|
543
|
+
from ..store.home import Store
|
|
544
|
+
|
|
545
|
+
try:
|
|
546
|
+
out = settle_rule_receipts(Store(home))
|
|
547
|
+
except Exception as exc: # the fit goes on; the receipts wait for the next one
|
|
548
|
+
note = f"receipts scored before 0.2.7 were not re-scored: {type(exc).__name__}: {exc}"[:300]
|
|
549
|
+
print(f"note: {note}", file=sys.stderr)
|
|
550
|
+
return [], note
|
|
551
|
+
failed = out["failed"]
|
|
552
|
+
if not failed:
|
|
553
|
+
return out["rescored"], None
|
|
554
|
+
shown = "; ".join(failed[:3]) + (f"; and {len(failed) - 3} more" if len(failed) > 3 else "")
|
|
555
|
+
note = f"receipts scored before 0.2.7 are not all settled yet; the next fit does what is left: {shown}"
|
|
556
|
+
print(f"note: {note}", file=sys.stderr)
|
|
557
|
+
return out["rescored"], note
|
|
558
|
+
|
|
559
|
+
|
|
516
560
|
def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), full: bool = False,
|
|
517
561
|
now: datetime | None = None, docs: Iterable[dict] | None = None, bundle_dir: Path | None = None,
|
|
518
562
|
benchmarks: Path | None = None, eb: bool = True, wait_s: float = 0.0) -> Path:
|
|
519
|
-
"""Build the belief and write `fits/<fit>/`; returns its path.
|
|
563
|
+
"""Build the belief and write `fits/<fit>/`; returns its path. A fit of the store's own runs (`docs` None)
|
|
564
|
+
first settles the receipts scored before 0.2.7 (`_settle_receipts`).
|
|
520
565
|
|
|
521
|
-
Raises FitBusy, or UnknownSource / NothingToFit before
|
|
566
|
+
Raises FitBusy, or UnknownSource / NothingToFit before any fit file is written.
|
|
522
567
|
"""
|
|
523
568
|
home = Path(home)
|
|
524
569
|
fits = home / "fits"
|
|
@@ -527,6 +572,7 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
527
572
|
without = tuple(without)
|
|
528
573
|
with fit_lock(home, wait_s):
|
|
529
574
|
remove_partials(home)
|
|
575
|
+
settled, settle_note = _settle_receipts(home) if docs is None else ([], None)
|
|
530
576
|
config = _read_config(home)
|
|
531
577
|
raw_weight = config.get("benchmark_prior_weight") # unset: each benchmark's own weight (spec 04 section 5)
|
|
532
578
|
weight = None if raw_weight is None else float(raw_weight)
|
|
@@ -610,7 +656,10 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
610
656
|
_atomic_json(partial / "design.json", design)
|
|
611
657
|
meta = {
|
|
612
658
|
"fit": fit_id, "created_at": now.isoformat(timespec="seconds"), "code_version": _code_version(),
|
|
613
|
-
"seed_key": seed_key, "
|
|
659
|
+
"seed_key": seed_key, **({"outcome_q": custom_q(q_by_tier)} if custom_q(q_by_tier) else {}),
|
|
660
|
+
**({"receipts_rescored": len(settled)} if settled else {}),
|
|
661
|
+
**({"notes": [settle_note]} if settle_note else {}),
|
|
662
|
+
"timebox_effort": design_rows.TIMEBOX_EFFORT_COST,
|
|
614
663
|
"timebox_terms": list(design_rows.TIMEBOX_LEVELS),
|
|
615
664
|
"options": {"no_prior": no_prior, "without": list(without), "full": full, "eb": eb,
|
|
616
665
|
"benchmark_prior_weight": weight, "benchmark_weights": bench_weights},
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
with reason `late:<name>`.
|
|
10
10
|
- `q` comes from the weakest tier used: verified 0.98, reported 0.95, heuristic 0.8, asserted 0.7.
|
|
11
11
|
- `scores`: every measured score, raw, whatever the rule is.
|
|
12
|
-
- D22: a shared run (`run.ext["dev.loopmath.share"]`) returns the sender's carried
|
|
12
|
+
- D22: a shared run (`run.ext["dev.loopmath.share"]`) returns the sender's carried outcome; a share that
|
|
13
|
+
carries no q takes the tier's q from `q_by_tier`.
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
from __future__ import annotations
|
|
@@ -60,17 +61,29 @@ def _last(signals: list[dict]) -> dict | None:
|
|
|
60
61
|
return indexed[-1][1]
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
def
|
|
64
|
-
|
|
64
|
+
def _shared_scores(raw: Any) -> dict[str, float]:
|
|
65
|
+
"""A share's scores: the list `share` writes (`{name, value, ...}`), or an object of name to value."""
|
|
66
|
+
if isinstance(raw, list):
|
|
67
|
+
pairs = [(s.get("name"), s.get("value")) for s in raw if isinstance(s, dict)]
|
|
68
|
+
elif isinstance(raw, dict):
|
|
69
|
+
pairs = list(raw.items())
|
|
70
|
+
else:
|
|
71
|
+
return {}
|
|
72
|
+
return {str(k): float(v) for k, v in pairs if k is not None and _is_number(v)}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _shared_evidence(share: dict, qmap: dict[str, float]) -> Evidence | None:
|
|
76
|
+
"""The outcome a shared run carries: `outcome` as `share` writes it, else an `evidence` object or z and q
|
|
77
|
+
at the top of the ext (older shapes). With no carried q, `qmap` gives the tier's q, as for a local run."""
|
|
78
|
+
ev = next((share[k] for k in ("outcome", "evidence") if isinstance(share.get(k), dict)), share)
|
|
65
79
|
if "z" not in ev and "q" not in ev:
|
|
66
80
|
return None
|
|
67
81
|
z = ev.get("z")
|
|
68
82
|
tier = _tier(ev.get("tier"))
|
|
69
83
|
q = ev.get("q")
|
|
70
|
-
scores = share.get("scores") if
|
|
71
|
-
return Evidence(z=None if z is None else float(z), q=float(q) if q is not None else
|
|
72
|
-
scores=
|
|
73
|
-
reasons=("shared",))
|
|
84
|
+
scores = share.get("scores") if share.get("scores") is not None else ev.get("scores")
|
|
85
|
+
return Evidence(z=None if z is None else float(z), q=float(q) if q is not None else float(qmap[tier]), tier=tier,
|
|
86
|
+
scores=_shared_scores(scores), reasons=("shared",))
|
|
74
87
|
|
|
75
88
|
|
|
76
89
|
def outcome_evidence(run_doc: dict, rule: AcceptanceRule, *, now: datetime,
|
|
@@ -79,7 +92,7 @@ def outcome_evidence(run_doc: dict, rule: AcceptanceRule, *, now: datetime,
|
|
|
79
92
|
run = run_doc.get("run") or {}
|
|
80
93
|
share = (run.get("ext") or {}).get("dev.loopmath.share")
|
|
81
94
|
if isinstance(share, dict):
|
|
82
|
-
carried = _shared_evidence(share)
|
|
95
|
+
carried = _shared_evidence(share, qmap)
|
|
83
96
|
if carried is not None:
|
|
84
97
|
return carried
|
|
85
98
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""One acceptance-rule resolver for the fit, `run finish` and its receipts, the share export and the runs view.
|
|
2
|
+
|
|
3
|
+
Before 0.2.7 the four readers each parsed `run.acceptance_rule` their own way. They now all call
|
|
4
|
+
`resolve_rule` and `evidence`, which give the fit's answers:
|
|
5
|
+
|
|
6
|
+
- `stored_rule(doc)` is the rule the fit has always read (`design.rule_from_doc`): a rule with no name is the
|
|
7
|
+
default, `window_days: 0` is 14 days and an empty `excludes_events` is revert and incident.
|
|
8
|
+
- The review case: a run onboarded from the logs, or stored by `run import` from 0.2.7, whose stored rule is
|
|
9
|
+
absent or the default (named `tests`, requiring exactly `tests`, no score), that has no `tests` verdict and
|
|
10
|
+
has a `review` or `accepted` verdict, is read by the review rule. So is a run that `loopmath outcome --rule
|
|
11
|
+
review` moved, whose stored rule carries the review mark.
|
|
12
|
+
- The review rule's reading: the latest `review` or `accepted` verdict, by observed time. Accept, approve and
|
|
13
|
+
pass are a success, reject and fail are not; any other value gives no outcome, with the reason
|
|
14
|
+
`unknown:review=<value>`. Late events, the window and q work as for any rule.
|
|
15
|
+
|
|
16
|
+
Only a rule with the review mark (`ext["dev.loopmath.rule"] = {"reading": "review"}`) is read this way, so a
|
|
17
|
+
user's own rule named `review` keeps its reading.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import dataclasses
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from ..types import DEFAULT_RULE, AcceptanceRule, Evidence
|
|
27
|
+
from .design import rule_from_doc
|
|
28
|
+
from .outcome import _last, outcome_evidence
|
|
29
|
+
|
|
30
|
+
RULE_EXT = "dev.loopmath.rule"
|
|
31
|
+
IMPORT_EXT = "dev.loopmath.import"
|
|
32
|
+
STORED_BY_IMPORT = "run import"
|
|
33
|
+
REVIEW_COMMAND = "loopmath outcome --rule review"
|
|
34
|
+
REVIEW_NAMES = ("review", "accepted")
|
|
35
|
+
REVIEW_PASS = ("accept", "approve", "pass")
|
|
36
|
+
REVIEW_FAIL = ("reject", "fail")
|
|
37
|
+
REVIEW_RULE = AcceptanceRule(
|
|
38
|
+
name="review", definition="the latest review or accepted verdict is accept, approve or pass",
|
|
39
|
+
requires=("review",), extra={"ext": {RULE_EXT: {"reading": "review"}}})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def stored_rule(doc: dict) -> AcceptanceRule:
|
|
43
|
+
"""The rule recorded on the run as the fit reads it. Raises ValueError or TypeError on a malformed score."""
|
|
44
|
+
return rule_from_doc(doc)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_review_rule(rule: AcceptanceRule | dict | None) -> bool:
|
|
48
|
+
"""A rule that carries the review mark (a rule object, or a rule as stored on a run)."""
|
|
49
|
+
if isinstance(rule, AcceptanceRule):
|
|
50
|
+
ext = rule.extra.get("ext")
|
|
51
|
+
elif isinstance(rule, dict):
|
|
52
|
+
ext = rule.get("ext")
|
|
53
|
+
else:
|
|
54
|
+
return False
|
|
55
|
+
mark = ext.get(RULE_EXT) if isinstance(ext, dict) else None
|
|
56
|
+
return isinstance(mark, dict) and mark.get("reading") == "review"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def review_rule_dict() -> dict[str, Any]:
|
|
60
|
+
"""The review rule as `outcome --rule review` stores it on a run."""
|
|
61
|
+
return {k: v for k, v in REVIEW_RULE.to_dict().items() if v is not None}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _verdicts(doc: dict) -> list[dict]:
|
|
65
|
+
signals = (doc.get("run") or {}).get("signals") or []
|
|
66
|
+
return [s for s in signals if isinstance(s, dict) and s.get("kind") == "verdict"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def is_marked_import(doc: dict) -> bool:
|
|
70
|
+
ext = (doc.get("run") or {}).get("ext")
|
|
71
|
+
mark = ext.get(IMPORT_EXT) if isinstance(ext, dict) else None
|
|
72
|
+
return isinstance(mark, dict) and mark.get("stored_by") == STORED_BY_IMPORT
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def has_default_rule(doc: dict) -> bool:
|
|
76
|
+
"""The stored rule is absent, or named `tests` and requiring exactly `tests`, with no score."""
|
|
77
|
+
try:
|
|
78
|
+
rule = stored_rule(doc)
|
|
79
|
+
except (TypeError, ValueError):
|
|
80
|
+
return False
|
|
81
|
+
return rule.name == "tests" and tuple(rule.requires) == ("tests",) and rule.score is None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def review_only(doc: dict) -> bool:
|
|
85
|
+
"""No `tests` verdict, and a `review` or `accepted` one."""
|
|
86
|
+
names = {s.get("name") for s in _verdicts(doc)}
|
|
87
|
+
return "tests" not in names and bool(names & set(REVIEW_NAMES))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def in_review_case(doc: dict) -> bool:
|
|
91
|
+
from ..store.runs import is_history
|
|
92
|
+
|
|
93
|
+
return (is_history(doc) or is_marked_import(doc)) and has_default_rule(doc) and review_only(doc)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def could_use_review_rule(doc: dict, rule: AcceptanceRule, z: float | None) -> bool:
|
|
97
|
+
"""A run `loopmath outcome --rule review` could help, as `runs` lists it: finished, read under another rule
|
|
98
|
+
with no outcome, with no `tests` verdict and a `review` or `accepted` one."""
|
|
99
|
+
from ..store.runs import is_finished
|
|
100
|
+
|
|
101
|
+
return z is None and not is_review_rule(rule) and is_finished(doc) and review_only(doc)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def resolve_rule(doc: dict) -> AcceptanceRule:
|
|
105
|
+
"""The rule every reader applies to this run. Raises as `stored_rule` does on a malformed score."""
|
|
106
|
+
raw = (doc.get("run") or {}).get("acceptance_rule")
|
|
107
|
+
if is_review_rule(raw) or in_review_case(doc):
|
|
108
|
+
return REVIEW_RULE
|
|
109
|
+
return stored_rule(doc)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def resolve_rule_or_default(doc: dict) -> AcceptanceRule:
|
|
113
|
+
"""`resolve_rule`, with the default rule for a malformed stored rule (receipts and views; the fit drops such a
|
|
114
|
+
run instead)."""
|
|
115
|
+
try:
|
|
116
|
+
return resolve_rule(doc)
|
|
117
|
+
except (TypeError, ValueError):
|
|
118
|
+
return DEFAULT_RULE
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def evidence(doc: dict, rule: AcceptanceRule, *, now: datetime,
|
|
122
|
+
q_by_tier: dict[str, float] | None = None) -> Evidence:
|
|
123
|
+
"""The run's outcome under `rule`: the review reading for the review rule, `outcome_evidence` otherwise."""
|
|
124
|
+
if not is_review_rule(rule):
|
|
125
|
+
return outcome_evidence(doc, rule, now=now, q_by_tier=q_by_tier)
|
|
126
|
+
run = doc.get("run") or {}
|
|
127
|
+
signals = [s for s in run.get("signals") or [] if isinstance(s, dict)]
|
|
128
|
+
named = [s for s in signals if s.get("kind") == "verdict" and s.get("name") in REVIEW_NAMES]
|
|
129
|
+
latest = _last(named)
|
|
130
|
+
value = str(latest.get("value") or "").lower() if latest is not None else None
|
|
131
|
+
reading = "accept" if value in REVIEW_PASS else "reject" if value in REVIEW_FAIL else None
|
|
132
|
+
kept = [s for s in signals if not (s.get("kind") == "verdict" and s.get("name") in REVIEW_NAMES)]
|
|
133
|
+
if reading is not None:
|
|
134
|
+
kept.append({**latest, "name": "review", "value": reading})
|
|
135
|
+
ev = outcome_evidence({**doc, "run": {**run, "signals": kept}}, rule, now=now, q_by_tier=q_by_tier)
|
|
136
|
+
if latest is not None and reading is None:
|
|
137
|
+
reasons = tuple(f"unknown:review={value}" if r == "missing:review" else r for r in ev.reasons)
|
|
138
|
+
ev = dataclasses.replace(ev, reasons=reasons)
|
|
139
|
+
return ev
|
|
@@ -227,7 +227,10 @@ def _add_recording(sub) -> None:
|
|
|
227
227
|
"Claude Code session id, a Codex thread id, or self for this Claude Code session (repeatable); "
|
|
228
228
|
"PIECE= names its piece; run sessions lists the ids. Only the part of a session inside the window "
|
|
229
229
|
"counts; a Codex session counts the usage it logged inside the window: each reading's increase at "
|
|
230
|
-
"the reading's time, a reading on either edge included"
|
|
230
|
+
"the reading's time, a reading on either edge included. A sub-agent or Codex child thread counts in "
|
|
231
|
+
"full when it started inside the window, not at all when it started before; a Codex reading lower "
|
|
232
|
+
"than the one before it (a compaction reset) counts from zero; a Codex session with a reading that "
|
|
233
|
+
"has no time, or whose increases inside the window add up to less than zero, counts whole, with a note")
|
|
231
234
|
q.add_argument("--cwd", action="append", default=[], metavar="[PIECE=]PATH", help="a folder an agent with no session id "
|
|
232
235
|
"worked in; its log is matched at finish by folder and time (repeatable)")
|
|
233
236
|
q.add_argument("--since", default=None, metavar="TS", help="when the work began (e.g. 2h or a local ISO time); with "
|
|
@@ -256,6 +259,14 @@ def _add_recording(sub) -> None:
|
|
|
256
259
|
target.add_argument("--run", default=None, metavar="RUN", help="the run the signal is about")
|
|
257
260
|
target.add_argument("--slate", default=None, metavar="SLT", help="the slate a preference is about (with --prefer)")
|
|
258
261
|
target.add_argument("--commit", default=None, metavar="SHA", help="late event: find runs by commit")
|
|
262
|
+
target.add_argument("--rule", default=None, metavar="NAME",
|
|
263
|
+
help="review: judge each RUN by its latest review or accepted verdict (accept, approve or "
|
|
264
|
+
"pass is a success); --undo-rule undoes that, and so does naming the rule a run had before "
|
|
265
|
+
"(tests when it had none) unless it is named review. A run with a tests verdict is refused, "
|
|
266
|
+
"and nothing is written if any RUN is refused")
|
|
267
|
+
target.add_argument("--undo-rule", action="store_true",
|
|
268
|
+
help="restore each RUN's rule from before --rule review moved it, exactly as it was")
|
|
269
|
+
p.add_argument("rule_runs", nargs="*", default=[], metavar="RUN", help="with --rule or --undo-rule: the runs to move")
|
|
259
270
|
p.add_argument("--signal", default=None, metavar="NAME=VALUE", help="NAME= (empty) declares an unmeasured score")
|
|
260
271
|
p.add_argument("--kind", default="verdict", choices=["verdict", "score", "event"], help="what the signal is (default verdict)")
|
|
261
272
|
p.add_argument("--unit", default=None, metavar="U", help="score unit")
|
|
@@ -294,6 +305,12 @@ def _add_recording(sub) -> None:
|
|
|
294
305
|
q.add_argument("value", metavar="VALUE")
|
|
295
306
|
_common(q)
|
|
296
307
|
q.set_defaults(func=_lazy("loopmath.store.commands:config_set"))
|
|
308
|
+
q = cs.add_parser("unset", help="remove one key, so it reads its default",
|
|
309
|
+
description="Removes one key from config.toml and keeps every other line, comments too. "
|
|
310
|
+
"A key that is not in the file is not an error.")
|
|
311
|
+
q.add_argument("key", metavar="KEY")
|
|
312
|
+
_common(q)
|
|
313
|
+
q.set_defaults(func=_lazy("loopmath.store.commands:config_unset"))
|
|
297
314
|
|
|
298
315
|
|
|
299
316
|
LABELER_KINDS = ("claude", "codex", "command")
|
|
@@ -453,13 +453,13 @@ ${a.hint ? `<dt>hint</dt><dd class="mono">${esc(a.hint)}</dd>` : ''}</dl>`;
|
|
|
453
453
|
}
|
|
454
454
|
function renderFoot() {
|
|
455
455
|
const kinds = RUN.edges_by_kind_tier || {};
|
|
456
|
-
const unpriced = N.filter(n => n.usd == null).length
|
|
456
|
+
const unpriced = N.filter(n => n.usd == null).length, many = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
457
457
|
// Up to three of this page's own names, in start order: one per name prefix first, then the next ones.
|
|
458
458
|
const picked = [], prefixes = new Set();
|
|
459
459
|
N.forEach((n, i) => { const prefix = String(n.lbl).replace(/-\d+$/, ''); if (picked.length < 3 && !prefixes.has(prefix)) { prefixes.add(prefix); picked.push(i); } });
|
|
460
460
|
N.forEach((n, i) => { if (picked.length < 3 && !picked.includes(i)) picked.push(i); });
|
|
461
461
|
const names = picked.sort((a, b) => a - b).map(i => esc(N[i].lbl)).join(', ');
|
|
462
|
-
$('foot').innerHTML = `Data: ${esc(RUN.source)}. Workspace ${esc(RUN.workspace)}, ${fmt.ts(RUN.start)} to ${fmt.ts(RUN.end)}. ${N.length}
|
|
462
|
+
$('foot').innerHTML = `Data: ${esc(RUN.source)}. Workspace ${esc(RUN.workspace)}, ${fmt.ts(RUN.start)} to ${fmt.ts(RUN.end)}. ${many(N.length, 'attempt')}, ${E.length} of ${RUN.n_source_edges} supplied edges shown (${Object.entries(kinds).map(([key, value]) => value + ' ' + key.replace('/', ' ')).join(', ')}), ${many(RUN.n_artifacts, 'artifact')} of which ${RUN.n_consumed} ${RUN.n_consumed === 1 ? 'was' : 'were'} read by another attempt. Known cost ${fmt.usd(N.reduce((sum, n) => sum + (n.usd == null ? 0 : n.usd), 0))} with ${many(unpriced, 'unpriced attempt')}. Attempt status is unavailable because the graph model carries no acceptance signal. Attempt names${names ? ` (${names})` : ''} are display names assigned in start order, from the role or, for an attempt with no known role, from the tool that ran it; hover a name for the mapped attempt id. Edge tiers: verified means both ends are present in the logs, heuristic means inferred from timing or text. Self-contained page, no external assets.`;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
const api = { N, E, A, RUN, ROLES, MODELS, ROLE_COLOR, MODEL_COLOR, MAX_USD, fmt, esc, roleName, color, dashed, radius, svg, text, curve, vcurve, arc, timeTicks, edgeClass, nodeClass, edgeVisible, artVisible };
|