bantamkit 0.34.3__tar.gz → 0.35.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bantamkit-0.34.3 → bantamkit-0.35.0}/PKG-INFO +9 -6
- {bantamkit-0.34.3 → bantamkit-0.35.0}/README.md +8 -5
- bantamkit-0.35.0/_assets/tools/shiftwork_plan.json +25 -0
- bantamkit-0.35.0/_assets/tools/work_plan.json +49 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/__init__.py +1 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/eventlog.py +5 -3
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/mcpserver.py +21 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/__main__.py +1 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/shiftwork.py +68 -1
- bantamkit-0.35.0/src/bantamkit/workplan.py +157 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/served-tool-surface.json +69 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_eventlog.py +1 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_layers.py +4 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_mcpserver.py +10 -3
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_compact_tool.py +3 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_served_tool_count_records.py +144 -18
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_shiftwork.py +167 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_skillaudit.py +13 -3
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_status_surface.py +1 -1
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_tokenledger.py +2 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_tool_manifest.py +2 -0
- bantamkit-0.35.0/tests/test_workplan.py +341 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/.gitignore +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/contracts/default.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/manifest.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/HISTORY.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/README.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/fixtures/.gitkeep +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/fixtures/catalog.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/perturbations/task-completion.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/.gitkeep +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/extract-contact.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/extract-invoice.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/extract-order.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/extract-schedule.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/extract-versions.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-db-port.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-deploy.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-oncall.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/recall-owner.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-compare.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/evals/tasks/shop-total.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/pricing/default.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/profiles/default.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/profiles/patient.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/rubrics/.gitkeep +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/rubrics/code-quality.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/rubrics/grounded-completion.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/rubrics/task-completion.yaml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/schemas/shiftwork-checkpoint.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/skills/.gitkeep +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/skills/file-graph.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/skills/memory.md +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/.gitkeep +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/bantamkit_read.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/bantamkit_status.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/build_identity.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/document_list.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/document_read.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/file_graph.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/memory_compact.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/memory_dream.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/memory_recall.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/memory_save.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/repo_map.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/shiftwork_clock_in.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/shiftwork_clock_out.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/shiftwork_status.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/skill_audit.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/token_ledger.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/_assets/tools/validate_json.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/hatch_build.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/pyproject.toml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/agent.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/assets.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/budget.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/client.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/contract.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/criticreplay.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/critique.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/docmanifest.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/docread.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/evalrun.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/filegraph.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/hostinstall.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/loopguard.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/mcpreport.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/__init__.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/component.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/divergence.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/dream.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/layers.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/memory/store.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/pdfread.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/pricing.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/profile.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/repomap.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/selfupdate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/skillaudit.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/statusline.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/structured.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/textutil.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/src/bantamkit/tokenledger.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/cli_exit_status_probe.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/conftest.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/bad-crc.docx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/charref-4301-digits.html +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/charset-table.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/compression-method-9.docx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/corrupt-deflate.docx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/encrypted-member.docx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/encrypted-mimetype.odt +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/internal-dtd-entity.docx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/rfc2231-charset.eml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/rfc822-nested-twice.eml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/docread/x-uuencode.eml +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/f8404ab-perturbation-baseline.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/data/platform-assumption-baseline.json +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/docread_fixtures.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/perturbation_baseline_harness.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/rbp16_effect_probe.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/rbp18_payload_probe.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_adapter.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_agent.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_amendguard.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_bantamkit_gitignore.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_bantamkit_read_tool.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_budget.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_build_identity.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_client.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_compaction_corpus_survey.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_conformance.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_conformance_harness_resilience.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_conformance_suite_table_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_contract_fanout.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_criticreplay.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_critique.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_doc_commands_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_docread.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_docread_ceilings.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_document_manifest_parity.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_document_setup.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_document_tasks.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_document_tools.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_encoding_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_evalrun.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_field_program_gates.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_field_programs.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_filegraph.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_hostinstall.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_install_shape.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_ladder_statistics.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_launcher_which.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_loopguard.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_mcp_endpoint.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_mcpdrift.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_mcpreport.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_component.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_divergence.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_dream.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_layers.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_memory_store_tripwire.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_mutmatrix.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_newline_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_packaging.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_pdfread.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_pinharness_ledger.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_platform_assumption_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_pricing.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_repo_map_tool.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_repomap.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_selfupdate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_statusline.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_structured.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_thread_exception_gate.py +0 -0
- {bantamkit-0.34.3 → bantamkit-0.35.0}/tests/test_version_agreement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: bantamkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.35.0
|
|
4
4
|
Summary: Memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop, plus a Python library that lifts small-model agents. Install once, run offline.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Ink01101011/bantamkit
|
|
6
6
|
Project-URL: Repository, https://github.com/Ink01101011/bantamkit
|
|
@@ -55,7 +55,7 @@ easy.
|
|
|
55
55
|
| [Configuration](#configuration) | Every flag and variable, with its default |
|
|
56
56
|
| [Update](#update) | `--update`, pipx, uv, then restart |
|
|
57
57
|
| [Troubleshooting](#troubleshooting) | Timeouts, refusals, the wrong store |
|
|
58
|
-
| [What it serves](#what-it-serves) |
|
|
58
|
+
| [What it serves](#what-it-serves) | 14 tools, one prompt, two resource templates |
|
|
59
59
|
| [Requirements](#requirements) | Python and dependencies |
|
|
60
60
|
| [The asset pack](#the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
|
|
61
61
|
| [The operator CLI: `python -m bantamkit.memory`](#the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
|
|
@@ -88,7 +88,8 @@ python -m venv <env>
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
`--install` records the venv's console script by absolute path with `"args": []`, so no launch
|
|
91
|
-
needs the network or your shell's PATH. Measured on macOS arm64
|
|
91
|
+
needs the network or your shell's PATH. Measured on macOS arm64 (served-tools: dated — the
|
|
92
|
+
surface was twelve then), it served 12 tools under a GUI
|
|
92
93
|
app's PATH, `/usr/bin:/bin:/usr/sbin:/sbin`; the Windows layout (`<env>\Scripts\`) was not.
|
|
93
94
|
|
|
94
95
|
**No network on the target? Carry a wheelhouse.** Download it on a machine with the **same OS,
|
|
@@ -96,9 +97,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
96
97
|
one platform only), copy `wheels/` across, and install from it:
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
python -m pip download "bantamkit[mcp]==0.
|
|
100
|
+
python -m pip download "bantamkit[mcp]==0.35.0" -d wheels
|
|
100
101
|
python -m venv <env>
|
|
101
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.
|
|
102
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.0"
|
|
102
103
|
<env>/bin/bantamkit-mcp --install cursor
|
|
103
104
|
```
|
|
104
105
|
|
|
@@ -272,7 +273,7 @@ you**; a new version with an old `build_id` means an old process.
|
|
|
272
273
|
|
|
273
274
|
## What it serves
|
|
274
275
|
|
|
275
|
-
|
|
276
|
+
It serves 14 tools, the `bantamkit_status` prompt and two resource templates
|
|
276
277
|
(`bantamkit://skills/{name}`, `bantamkit://rubrics/{name}`):
|
|
277
278
|
|
|
278
279
|
| Tool | What it does |
|
|
@@ -286,6 +287,8 @@ you**; a new version with an old `build_id` means an old process.
|
|
|
286
287
|
| `shiftwork_clock_in` | open a unit of work and get its brief |
|
|
287
288
|
| `shiftwork_clock_out` | close a unit with status and accounting |
|
|
288
289
|
| `shiftwork_status` | report the open cursor |
|
|
290
|
+
| `shiftwork_plan` | read-only: which units of a checkpoint its `depends_on` graph permits to run at once. Never moves the cursor |
|
|
291
|
+
| `work_plan` | turn any `{id, depends_on, priority}` graph into the batches that may run in parallel, plus the widest fan-out |
|
|
289
292
|
| `token_ledger` | what a session cost, read off the host's transcripts |
|
|
290
293
|
| `bantamkit_status` | report store health against its budget |
|
|
291
294
|
| `build_identity` | report the fingerprint of the source on disk, not the executing code — useful when a machine carries two installs under one name |
|
|
@@ -27,7 +27,7 @@ easy.
|
|
|
27
27
|
| [Configuration](#configuration) | Every flag and variable, with its default |
|
|
28
28
|
| [Update](#update) | `--update`, pipx, uv, then restart |
|
|
29
29
|
| [Troubleshooting](#troubleshooting) | Timeouts, refusals, the wrong store |
|
|
30
|
-
| [What it serves](#what-it-serves) |
|
|
30
|
+
| [What it serves](#what-it-serves) | 14 tools, one prompt, two resource templates |
|
|
31
31
|
| [Requirements](#requirements) | Python and dependencies |
|
|
32
32
|
| [The asset pack](#the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
|
|
33
33
|
| [The operator CLI: `python -m bantamkit.memory`](#the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
|
|
@@ -60,7 +60,8 @@ python -m venv <env>
|
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
`--install` records the venv's console script by absolute path with `"args": []`, so no launch
|
|
63
|
-
needs the network or your shell's PATH. Measured on macOS arm64
|
|
63
|
+
needs the network or your shell's PATH. Measured on macOS arm64 (served-tools: dated — the
|
|
64
|
+
surface was twelve then), it served 12 tools under a GUI
|
|
64
65
|
app's PATH, `/usr/bin:/bin:/usr/sbin:/sbin`; the Windows layout (`<env>\Scripts\`) was not.
|
|
65
66
|
|
|
66
67
|
**No network on the target? Carry a wheelhouse.** Download it on a machine with the **same OS,
|
|
@@ -68,9 +69,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
68
69
|
one platform only), copy `wheels/` across, and install from it:
|
|
69
70
|
|
|
70
71
|
```bash
|
|
71
|
-
python -m pip download "bantamkit[mcp]==0.
|
|
72
|
+
python -m pip download "bantamkit[mcp]==0.35.0" -d wheels
|
|
72
73
|
python -m venv <env>
|
|
73
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.
|
|
74
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.0"
|
|
74
75
|
<env>/bin/bantamkit-mcp --install cursor
|
|
75
76
|
```
|
|
76
77
|
|
|
@@ -244,7 +245,7 @@ you**; a new version with an old `build_id` means an old process.
|
|
|
244
245
|
|
|
245
246
|
## What it serves
|
|
246
247
|
|
|
247
|
-
|
|
248
|
+
It serves 14 tools, the `bantamkit_status` prompt and two resource templates
|
|
248
249
|
(`bantamkit://skills/{name}`, `bantamkit://rubrics/{name}`):
|
|
249
250
|
|
|
250
251
|
| Tool | What it does |
|
|
@@ -258,6 +259,8 @@ you**; a new version with an old `build_id` means an old process.
|
|
|
258
259
|
| `shiftwork_clock_in` | open a unit of work and get its brief |
|
|
259
260
|
| `shiftwork_clock_out` | close a unit with status and accounting |
|
|
260
261
|
| `shiftwork_status` | report the open cursor |
|
|
262
|
+
| `shiftwork_plan` | read-only: which units of a checkpoint its `depends_on` graph permits to run at once. Never moves the cursor |
|
|
263
|
+
| `work_plan` | turn any `{id, depends_on, priority}` graph into the batches that may run in parallel, plus the widest fan-out |
|
|
261
264
|
| `token_ledger` | what a session cost, read off the host's transcripts |
|
|
262
265
|
| `bantamkit_status` | report store health against its budget |
|
|
263
266
|
| `build_identity` | report the fingerprint of the source on disk, not the executing code — useful when a machine carries two installs under one name |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shiftwork_plan",
|
|
3
|
+
"description": "Shift-work plan: read-only batch view of a checkpoint — which units its `depends_on` graph permits to run in parallel. Answers `{\"result\": \"plan\", \"batches\", \"ready\", \"sequence\", \"width\", \"cursor\"}`: `ready` is `batches[0]`, the units dispatchable right now, and `width` is the largest batch length, the widest fan-out. Units with status `done` or `dropped` are satisfied — dropped from the graph, and edges pointing at them treated as already resolved; `todo`, `in_progress` and `blocked` stay in it. Every unit has priority 0, because the checkpoint schema has no priority field, so order inside a batch is `plan.units` order. Same refusal sentences as `work_plan` for a duplicate id, an unknown dependency or a cycle, and the same refusals `shiftwork_status` gives for a checkpoint it cannot read. It reports what the dependency graph permits; it does not move the cursor, which is echoed unchanged so the single-pointer contract and the batch view can be read side by side. An orchestrator stays responsible for what it actually dispatches: `depends_on` encodes logical order, not file contention. Never mutates.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"checkpoint": {
|
|
10
|
+
"title": "Checkpoint",
|
|
11
|
+
"type": "string"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"checkpoint"
|
|
16
|
+
],
|
|
17
|
+
"type": "object",
|
|
18
|
+
"title": "shiftwork_planArguments"
|
|
19
|
+
},
|
|
20
|
+
"output_schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": true,
|
|
23
|
+
"title": "shiftwork_planDictOutput"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "work_plan",
|
|
3
|
+
"description": "Work plan: turn a dependency graph into the batches that may run in parallel. Takes `nodes`, each `{id, depends_on, priority}` — `priority` defaults to 0 — and answers `{\"result\": \"plan\", \"batches\", \"sequence\", \"width\"}`: batch k holds every node whose dependencies all appear in batches below k, `sequence` is those batches flattened in order, and `width` is the largest batch length — the widest fan-out, which is the number an orchestrator needs to decide whether it can afford the batch. Order inside a batch is priority descending, then the order the nodes were given; both halves are contract, because determinism inside a batch is what two implementations are held to. Empty `nodes` is an ANSWER, not a refusal: no batches, no sequence, width 0. Three refusals, checked in this order and one sentence each: `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. It reports what the graph permits and decides nothing about what may actually be dispatched together — `depends_on` encodes logical order, not file contention, so a batch this tool calls parallel may still hold work that writes one file. Pure computation over the nodes it was handed: it opens no file and takes no path. Never mutates.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"nodes": {
|
|
10
|
+
"title": "Nodes",
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The node's identifier, unique across `nodes`"
|
|
18
|
+
},
|
|
19
|
+
"depends_on": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"description": "Ids this node waits for; every one of them must be declared by some node"
|
|
25
|
+
},
|
|
26
|
+
"priority": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"description": "Tie-break inside a batch, descending. Optional; 0 when absent"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"id",
|
|
33
|
+
"depends_on"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": [
|
|
39
|
+
"nodes"
|
|
40
|
+
],
|
|
41
|
+
"type": "object",
|
|
42
|
+
"title": "work_planArguments"
|
|
43
|
+
},
|
|
44
|
+
"output_schema": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": true,
|
|
47
|
+
"title": "work_planDictOutput"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -29,4 +29,4 @@ from bantamkit.structured import StructuredOutputError, extract_json, structured
|
|
|
29
29
|
# file as its dynamic version source, so the wheel's metadata and the string the MCP
|
|
30
30
|
# server advertises are the same committed bytes, and neither is a function of when
|
|
31
31
|
# someone last ran `pip`.
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.35.0"
|
|
@@ -30,11 +30,13 @@ THREE HARD RULES, each with the failure it prevents:
|
|
|
30
30
|
byte-compares both runtimes' streams; one stray write breaks the wire suite. Nothing
|
|
31
31
|
in this module touches `sys.stderr` or `sys.stdout`.
|
|
32
32
|
* **Metadata only.** Never a tool argument's value, never a memory body, never a
|
|
33
|
-
validated output, never a query, never a document row.
|
|
33
|
+
validated output, never a query, never a document row. Nine of the fourteen tools take
|
|
34
34
|
an unbounded string (counted over `assets/tools/`: a `string` parameter with no `enum`
|
|
35
35
|
or `maxLength`, or an array of such; `bantamkit_read` and `repo_map` were two more until
|
|
36
|
-
job50 I5 retired them from the roster
|
|
37
|
-
|
|
36
|
+
job50 I5 retired them from the roster — and `work_plan` is NOT one of them: its `nodes`
|
|
37
|
+
is an array of objects, not an array of strings, so the rule does not reach the ids
|
|
38
|
+
inside it) and among those `skill_audit`'s `root`,
|
|
39
|
+
`token_ledger`'s `root` and `prices` and the four shift-work tools' `checkpoint` are
|
|
38
40
|
absolute paths; each record carries counts and tokens from a closed set, never the
|
|
39
41
|
path, never a part name, never a skill id, never a mapped file, never a session id,
|
|
40
42
|
never a model name. The dormant `bantamkit_read` and `repo_map` handlers keep the same
|
|
@@ -30,6 +30,7 @@ from bantamkit import (
|
|
|
30
30
|
shiftwork,
|
|
31
31
|
skillaudit,
|
|
32
32
|
tokenledger,
|
|
33
|
+
workplan,
|
|
33
34
|
)
|
|
34
35
|
from bantamkit.assets import AssetNotFound, assets_root, load_skill, load_tool_asset
|
|
35
36
|
from bantamkit.client import BantamError
|
|
@@ -1474,6 +1475,24 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1474
1475
|
_record_result(log, "shiftwork_status", lambda: shiftwork.status(checkpoint))
|
|
1475
1476
|
)
|
|
1476
1477
|
|
|
1478
|
+
def work_plan(nodes: list[dict[str, Any]]) -> dict[str, Any]:
|
|
1479
|
+
# `result` is added HERE and not in `workplan.plan`, which is Layer 1 and answers
|
|
1480
|
+
# the computation (`batches`, `sequence`, `width`) rather than a wire shape. The
|
|
1481
|
+
# wire shape is the tool asset's, so the verdict key is put on at the seam that
|
|
1482
|
+
# serves it — the same division `plan_batches` uses one layer down. A refusal
|
|
1483
|
+
# already carries its own `result` and passes through untouched, because
|
|
1484
|
+
# `_record_result` reads that key to write the register's verdict to the log.
|
|
1485
|
+
def answered() -> dict[str, Any]:
|
|
1486
|
+
plan = workplan.plan(nodes)
|
|
1487
|
+
return plan if plan.get("result") == "error" else {"result": "plan", **plan}
|
|
1488
|
+
|
|
1489
|
+
return _noted_dict(_record_result(log, "work_plan", answered))
|
|
1490
|
+
|
|
1491
|
+
def shiftwork_plan(checkpoint: str) -> dict[str, Any]:
|
|
1492
|
+
return _noted_dict(
|
|
1493
|
+
_record_result(log, "shiftwork_plan", lambda: shiftwork.plan_batches(checkpoint))
|
|
1494
|
+
)
|
|
1495
|
+
|
|
1477
1496
|
# A TOOL and not a resource or an `initialize` field, because the gap RB-P84 names is
|
|
1478
1497
|
# an AGENT MID-CALL: the host reads `serverInfo` once at handshake and the
|
|
1479
1498
|
# tool-calling model never sees it, and `resources/read` is a host-facing surface
|
|
@@ -1832,6 +1851,8 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1832
1851
|
_from_manifest(skill_audit, "skill_audit"),
|
|
1833
1852
|
_from_manifest(memory_dream, "memory_dream"),
|
|
1834
1853
|
_from_manifest(token_ledger, "token_ledger"),
|
|
1854
|
+
_from_manifest(work_plan, "work_plan"),
|
|
1855
|
+
_from_manifest(shiftwork_plan, "shiftwork_plan"),
|
|
1835
1856
|
]
|
|
1836
1857
|
|
|
1837
1858
|
server = MCPServer(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`docs/memory.md` states, as a design decision, that `lint`, `archived` and
|
|
4
4
|
`restore` are **not** agent tools — "lifecycle is an operator decision, not a
|
|
5
|
-
model decision" — and the
|
|
5
|
+
model decision" — and the fourteen-tool surface holds to it; `compact` is the one
|
|
6
6
|
exception since job42 (`memory_compact`, the on-refusal path the model reaches
|
|
7
7
|
itself). That position is only coherent if the operator can actually make the
|
|
8
8
|
decision. Measured
|
|
@@ -67,7 +67,9 @@ a missing one.
|
|
|
67
67
|
|
|
68
68
|
Cursor advance is v1-linear: it moves to the first non-terminal unit in plan
|
|
69
69
|
order and ignores `depends_on` — non-linear plans need a planner unit to
|
|
70
|
-
reorder `plan.units` first.
|
|
70
|
+
reorder `plan.units` first. `plan_batches` below READS `depends_on` and answers
|
|
71
|
+
the batch view, so the module no longer ignores the field; what still ignores it
|
|
72
|
+
is CURSOR ADVANCE, and the batch view is read-only and moves nothing.
|
|
71
73
|
|
|
72
74
|
No lock: the MCP topology has one orchestrator by construction. The driver's
|
|
73
75
|
O_EXCL lock guards cross-process races this shape does not have, and clock-out
|
|
@@ -84,6 +86,7 @@ from typing import Any
|
|
|
84
86
|
|
|
85
87
|
import jsonschema
|
|
86
88
|
|
|
89
|
+
from bantamkit import workplan
|
|
87
90
|
from bantamkit.assets import AssetNotFound, load_schema, load_tool_asset
|
|
88
91
|
from bantamkit.contract import schema_error
|
|
89
92
|
|
|
@@ -550,3 +553,67 @@ def status(checkpoint: str) -> dict[str, Any]:
|
|
|
550
553
|
"open_questions": len(document["handoff"]["open_questions"]),
|
|
551
554
|
"last_history": history[-1] if history else None,
|
|
552
555
|
}
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def plan_batches(checkpoint: str) -> dict[str, Any]:
|
|
559
|
+
"""Read-only batch view of a checkpoint. Never mutates.
|
|
560
|
+
|
|
561
|
+
The adapter over `workplan.plan`, and it holds no graph logic of its own: a loop over
|
|
562
|
+
`depends_on` here would be Layer 1's work done in Layer 5. Its whole content is the
|
|
563
|
+
three decisions below plus the shape it answers in.
|
|
564
|
+
|
|
565
|
+
**A `done` or `dropped` unit is SATISFIED**, which is two things and not one: it
|
|
566
|
+
leaves the graph, AND every edge pointing at it is treated as already resolved. Only
|
|
567
|
+
the first would be a defect rather than a simplification — `workplan.plan` refuses an
|
|
568
|
+
edge into an id no node declares, so dropping the unit while keeping the edge would
|
|
569
|
+
make every checkpoint with one finished unit unplannable, which is every checkpoint
|
|
570
|
+
after its first clock-out. `todo`, `in_progress` and `blocked` are all still work and
|
|
571
|
+
all stay in. The terminal pair is `TERMINAL_UNIT_STATUS`, the same constant the
|
|
572
|
+
driver's success test uses, so "finished" means one thing in this module.
|
|
573
|
+
|
|
574
|
+
**Every unit gets priority 0.** The checkpoint schema has no priority field and this
|
|
575
|
+
design does not add one, so the tie-break inside a batch falls through to the core's
|
|
576
|
+
insertion order — `plan.units` order, which is the order a reader of the checkpoint
|
|
577
|
+
already sees.
|
|
578
|
+
|
|
579
|
+
**The cursor is ECHOED, never written.** `clock_out` remains the only thing that
|
|
580
|
+
moves it and stays v1-linear; this tool reports what the graph PERMITS beside the
|
|
581
|
+
single pointer that says what the driver will actually do next, so an orchestrator
|
|
582
|
+
can read the two side by side and decide. Advisory, in one direction only.
|
|
583
|
+
|
|
584
|
+
Returns `{"result": "plan", "batches", "ready", "sequence", "width", "cursor"}` —
|
|
585
|
+
`ready` is `batches[0]`, or `[]` when the plan is all terminal, which is an ANSWER
|
|
586
|
+
and not a refusal. Refusals pass through verbatim in both directions: `_read_valid`'s
|
|
587
|
+
for a checkpoint that cannot be read or does not validate, and the core's own
|
|
588
|
+
duplicate-id / unknown-dependency / cycle sentences for a graph that cannot batch.
|
|
589
|
+
"""
|
|
590
|
+
document, refusal = _read_valid(Path(checkpoint))
|
|
591
|
+
if refusal is not None:
|
|
592
|
+
return refusal
|
|
593
|
+
nodes = [
|
|
594
|
+
# `depends_on` is required by the schema, so `_read_valid` has already refused a
|
|
595
|
+
# unit without it and the default below cannot fire here. It is written anyway
|
|
596
|
+
# because the Node adapter reaches the same mapping and MUST default it too: a
|
|
597
|
+
# default on one side only is how two runtimes come to disagree about real data.
|
|
598
|
+
{"id": unit["id"], "depends_on": list(unit.get("depends_on") or []), "priority": 0}
|
|
599
|
+
for unit in document["plan"]["units"]
|
|
600
|
+
if unit["status"] not in TERMINAL_UNIT_STATUS
|
|
601
|
+
]
|
|
602
|
+
satisfied = {
|
|
603
|
+
unit["id"] for unit in document["plan"]["units"] if unit["status"] in TERMINAL_UNIT_STATUS
|
|
604
|
+
}
|
|
605
|
+
for node in nodes:
|
|
606
|
+
node["depends_on"] = [dep for dep in node["depends_on"] if dep not in satisfied]
|
|
607
|
+
|
|
608
|
+
answer = workplan.plan(nodes)
|
|
609
|
+
if answer.get("result") == "error":
|
|
610
|
+
return answer
|
|
611
|
+
batches = answer["batches"]
|
|
612
|
+
return {
|
|
613
|
+
"result": "plan",
|
|
614
|
+
"batches": batches,
|
|
615
|
+
"ready": batches[0] if batches else [],
|
|
616
|
+
"sequence": answer["sequence"],
|
|
617
|
+
"width": answer["width"],
|
|
618
|
+
"cursor": document["plan"]["cursor"],
|
|
619
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Layer 1 — a deterministic work-plan planner: turn `depends_on` into batches.
|
|
2
|
+
|
|
3
|
+
`assets/schemas/shiftwork-checkpoint.json` has carried `plan.units[].depends_on` since
|
|
4
|
+
the contract was written and neither runtime has ever read it: cursor advance is
|
|
5
|
+
v1-linear in both, so every job this repo ran was executed as a straight line whatever
|
|
6
|
+
its dependency graph said. Measured 2026-09-18 over the 19 checkpoints in `.shiftwork/`,
|
|
7
|
+
215 units collapse to 119 batches — 96 serial steps (44.7 %) were false serialization.
|
|
8
|
+
|
|
9
|
+
This module is the mechanism underneath that. It is **Layer 1 (Core)** per
|
|
10
|
+
docs/architecture.md: a pure function, no filesystem, no clock, no `mcp` import, no
|
|
11
|
+
dependency outside the standard library. The shift-work adapter (Layer 5) and the tool
|
|
12
|
+
assets (Layer 2) are separate units in separate files, because a change lives in exactly
|
|
13
|
+
one layer.
|
|
14
|
+
|
|
15
|
+
The semantics are the documented defaults of `ex-flow@1.1.0` — the user's own MIT
|
|
16
|
+
package, which is what made the measurement above possible. It is the SPECIFICATION and
|
|
17
|
+
deliberately NOT a dependency: `runtime-ts/package.json` declares exactly one runtime
|
|
18
|
+
dependency, and `docs/porting.md` uses "no runtime dependency is allowed into
|
|
19
|
+
`runtime-ts`" as the live justification for three divergence rows. Taking `ex-flow`
|
|
20
|
+
would collapse that argument to save a hundred lines of graph code, and `runtime_py`
|
|
21
|
+
could not import it in any case. So both runtimes hand-write it and the conformance
|
|
22
|
+
suite proves they agreed. Design:
|
|
23
|
+
`docs/superpowers/specs/2026-09-18-workplan-dag-design.md`.
|
|
24
|
+
|
|
25
|
+
Determinism is not a nicety here — it is the whole gate. Two implementations that both
|
|
26
|
+
"topologically sort correctly" can still disagree about the order inside a batch and
|
|
27
|
+
about WHICH cycle they name in a graph that has two, and either disagreement turns the
|
|
28
|
+
conformance suite red for a reason that is not a defect. Hence the two ordering rules
|
|
29
|
+
below are stated as contract, not as an artifact of whatever the dict happened to do.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
__all__ = ["plan"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _cycle_path(order: list[str], deps: dict[str, list[str]], pending: set[str]) -> list[str]:
|
|
38
|
+
"""Name one cycle among the nodes Kahn could not emit, reproducibly in any language.
|
|
39
|
+
|
|
40
|
+
A graph can hold several cycles and every one of them is an equally correct answer,
|
|
41
|
+
so "find a cycle" is not a specification — two runtimes would name two different
|
|
42
|
+
ones and the differential would go red over nothing. The rule is therefore fixed:
|
|
43
|
+
scan the un-emitted nodes in INPUT order, walk `depends_on` in DECLARED order, and
|
|
44
|
+
the first repeated id closes the cycle. First node scanned, first edge walked, first
|
|
45
|
+
repeat wins.
|
|
46
|
+
|
|
47
|
+
Only un-emitted dependencies are walked. An id Kahn already emitted sits in a batch
|
|
48
|
+
below and can never be part of a cycle, so following one would be a dead end rather
|
|
49
|
+
than a different answer.
|
|
50
|
+
|
|
51
|
+
The walk may begin at a node that is merely downstream of a cycle rather than in one
|
|
52
|
+
(`a -> b -> c -> b`: `a` cannot be emitted, but `a` is not in the cycle). What is
|
|
53
|
+
returned is the CYCLE, `b -> c -> b`, trimmed at the first occurrence of the repeated
|
|
54
|
+
id — the tail that only led into it is not part of it.
|
|
55
|
+
"""
|
|
56
|
+
start = next(node_id for node_id in order if node_id in pending)
|
|
57
|
+
path = [start]
|
|
58
|
+
seen = {start: 0}
|
|
59
|
+
current = start
|
|
60
|
+
while True:
|
|
61
|
+
nxt = next((dep for dep in deps[current] if dep in pending), None)
|
|
62
|
+
if nxt is None:
|
|
63
|
+
# Unreachable for a graph Kahn stalled on: a node is only left pending
|
|
64
|
+
# because at least one dependency of it is also still pending.
|
|
65
|
+
return path + [path[0]]
|
|
66
|
+
if nxt in seen:
|
|
67
|
+
return path[seen[nxt]:] + [nxt]
|
|
68
|
+
seen[nxt] = len(path)
|
|
69
|
+
path.append(nxt)
|
|
70
|
+
current = nxt
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def plan(nodes: list[dict[str, Any]]) -> dict[str, Any]:
|
|
74
|
+
"""Batch a dependency graph into the levels that may run in parallel.
|
|
75
|
+
|
|
76
|
+
nodes: [{"id": str, "depends_on": list[str], "priority": int}] — `depends_on`
|
|
77
|
+
defaults to empty and `priority` defaults to 0, because the shift-work checkpoint
|
|
78
|
+
schema has no priority field and this design does not add one.
|
|
79
|
+
|
|
80
|
+
Returns either
|
|
81
|
+
|
|
82
|
+
{"batches": [[id, ...], ...], "sequence": [id, ...], "width": int}
|
|
83
|
+
|
|
84
|
+
where batch *k* holds every node whose dependencies all appear in batches < *k*,
|
|
85
|
+
`sequence` is those batches flattened in order, and `width` is the largest batch
|
|
86
|
+
length (the widest fan-out, which is the number an orchestrator needs to decide
|
|
87
|
+
whether it can afford the batch) — or a structured refusal
|
|
88
|
+
|
|
89
|
+
{"result": "error", "reason": str}
|
|
90
|
+
|
|
91
|
+
which is the shape `shiftwork.py` already returns, so nothing new is invented for it.
|
|
92
|
+
|
|
93
|
+
**Ordering inside a batch is priority descending, then insertion order** — the order
|
|
94
|
+
the nodes were given, which for the shift-work adapter is `plan.units` order. Both
|
|
95
|
+
halves are contract: the second is what makes two independent implementations agree.
|
|
96
|
+
|
|
97
|
+
`plan([])` is `{"batches": [], "sequence": [], "width": 0}`. An empty plan is an
|
|
98
|
+
ANSWER, not a refusal — the same reasoning `shiftwork.clock_in` already applies when
|
|
99
|
+
its all-terminal test reports success instead of escalating on a dangling cursor.
|
|
100
|
+
|
|
101
|
+
The three refusals are checked in a fixed order — duplicate id, then unknown
|
|
102
|
+
dependency, then cycle — so a graph carrying two faults always names the same one.
|
|
103
|
+
The later checks would be meaningless on the earlier faults' input anyway: a
|
|
104
|
+
duplicate id makes "which node declares this" ambiguous, and an edge into a node
|
|
105
|
+
that does not exist is not a cycle.
|
|
106
|
+
"""
|
|
107
|
+
order: list[str] = []
|
|
108
|
+
deps: dict[str, list[str]] = {}
|
|
109
|
+
priority: dict[str, int] = {}
|
|
110
|
+
|
|
111
|
+
for node in nodes:
|
|
112
|
+
node_id = node["id"]
|
|
113
|
+
if node_id in deps:
|
|
114
|
+
return {"result": "error", "reason": f"duplicate node id {node_id}"}
|
|
115
|
+
order.append(node_id)
|
|
116
|
+
deps[node_id] = list(node.get("depends_on") or [])
|
|
117
|
+
priority[node_id] = node.get("priority", 0)
|
|
118
|
+
|
|
119
|
+
for node_id in order:
|
|
120
|
+
for dep in deps[node_id]:
|
|
121
|
+
if dep not in deps:
|
|
122
|
+
return {
|
|
123
|
+
"result": "error",
|
|
124
|
+
"reason": f"node {node_id} depends on {dep}, which no node declares",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Insertion index is the tie-break, so it is carried explicitly rather than being
|
|
128
|
+
# left to the incidental order of a set or dict during the sweep below.
|
|
129
|
+
index = {node_id: i for i, node_id in enumerate(order)}
|
|
130
|
+
|
|
131
|
+
pending = set(order)
|
|
132
|
+
emitted: set[str] = set()
|
|
133
|
+
batches: list[list[str]] = []
|
|
134
|
+
|
|
135
|
+
while pending:
|
|
136
|
+
ready = [
|
|
137
|
+
node_id
|
|
138
|
+
for node_id in order
|
|
139
|
+
if node_id in pending and all(dep in emitted for dep in deps[node_id])
|
|
140
|
+
]
|
|
141
|
+
if not ready:
|
|
142
|
+
path = _cycle_path(order, deps, pending)
|
|
143
|
+
return {
|
|
144
|
+
"result": "error",
|
|
145
|
+
"reason": "the graph has a cycle: " + " -> ".join(path),
|
|
146
|
+
}
|
|
147
|
+
ready.sort(key=lambda node_id: (-priority[node_id], index[node_id]))
|
|
148
|
+
batches.append(ready)
|
|
149
|
+
pending.difference_update(ready)
|
|
150
|
+
emitted.update(ready)
|
|
151
|
+
|
|
152
|
+
sequence = [node_id for batch in batches for node_id in batch]
|
|
153
|
+
return {
|
|
154
|
+
"batches": batches,
|
|
155
|
+
"sequence": sequence,
|
|
156
|
+
"width": max((len(batch) for batch in batches), default=0),
|
|
157
|
+
}
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"memory_compact",
|
|
12
12
|
"skill_audit",
|
|
13
13
|
"memory_dream",
|
|
14
|
-
"token_ledger"
|
|
14
|
+
"token_ledger",
|
|
15
|
+
"work_plan",
|
|
16
|
+
"shiftwork_plan"
|
|
15
17
|
],
|
|
16
18
|
"tools": {
|
|
17
19
|
"bantamkit_status": {
|
|
@@ -449,6 +451,72 @@
|
|
|
449
451
|
"type": "object",
|
|
450
452
|
"title": "token_ledgerOutput"
|
|
451
453
|
}
|
|
454
|
+
},
|
|
455
|
+
"work_plan": {
|
|
456
|
+
"description": "Work plan: turn a dependency graph into the batches that may run in parallel. Takes `nodes`, each `{id, depends_on, priority}` — `priority` defaults to 0 — and answers `{\"result\": \"plan\", \"batches\", \"sequence\", \"width\"}`: batch k holds every node whose dependencies all appear in batches below k, `sequence` is those batches flattened in order, and `width` is the largest batch length — the widest fan-out, which is the number an orchestrator needs to decide whether it can afford the batch. Order inside a batch is priority descending, then the order the nodes were given; both halves are contract, because determinism inside a batch is what two implementations are held to. Empty `nodes` is an ANSWER, not a refusal: no batches, no sequence, width 0. Three refusals, checked in this order and one sentence each: `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. It reports what the graph permits and decides nothing about what may actually be dispatched together — `depends_on` encodes logical order, not file contention, so a batch this tool calls parallel may still hold work that writes one file. Pure computation over the nodes it was handed: it opens no file and takes no path. Never mutates.",
|
|
457
|
+
"inputSchema": {
|
|
458
|
+
"properties": {
|
|
459
|
+
"nodes": {
|
|
460
|
+
"title": "Nodes",
|
|
461
|
+
"type": "array",
|
|
462
|
+
"items": {
|
|
463
|
+
"type": "object",
|
|
464
|
+
"properties": {
|
|
465
|
+
"id": {
|
|
466
|
+
"type": "string",
|
|
467
|
+
"description": "The node's identifier, unique across `nodes`"
|
|
468
|
+
},
|
|
469
|
+
"depends_on": {
|
|
470
|
+
"type": "array",
|
|
471
|
+
"items": {
|
|
472
|
+
"type": "string"
|
|
473
|
+
},
|
|
474
|
+
"description": "Ids this node waits for; every one of them must be declared by some node"
|
|
475
|
+
},
|
|
476
|
+
"priority": {
|
|
477
|
+
"type": "integer",
|
|
478
|
+
"description": "Tie-break inside a batch, descending. Optional; 0 when absent"
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
"required": [
|
|
482
|
+
"id",
|
|
483
|
+
"depends_on"
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
"required": [
|
|
489
|
+
"nodes"
|
|
490
|
+
],
|
|
491
|
+
"type": "object",
|
|
492
|
+
"title": "work_planArguments"
|
|
493
|
+
},
|
|
494
|
+
"outputSchema": {
|
|
495
|
+
"type": "object",
|
|
496
|
+
"additionalProperties": true,
|
|
497
|
+
"title": "work_planDictOutput"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"shiftwork_plan": {
|
|
501
|
+
"description": "Shift-work plan: read-only batch view of a checkpoint — which units its `depends_on` graph permits to run in parallel. Answers `{\"result\": \"plan\", \"batches\", \"ready\", \"sequence\", \"width\", \"cursor\"}`: `ready` is `batches[0]`, the units dispatchable right now, and `width` is the largest batch length, the widest fan-out. Units with status `done` or `dropped` are satisfied — dropped from the graph, and edges pointing at them treated as already resolved; `todo`, `in_progress` and `blocked` stay in it. Every unit has priority 0, because the checkpoint schema has no priority field, so order inside a batch is `plan.units` order. Same refusal sentences as `work_plan` for a duplicate id, an unknown dependency or a cycle, and the same refusals `shiftwork_status` gives for a checkpoint it cannot read. It reports what the dependency graph permits; it does not move the cursor, which is echoed unchanged so the single-pointer contract and the batch view can be read side by side. An orchestrator stays responsible for what it actually dispatches: `depends_on` encodes logical order, not file contention. Never mutates.",
|
|
502
|
+
"inputSchema": {
|
|
503
|
+
"properties": {
|
|
504
|
+
"checkpoint": {
|
|
505
|
+
"title": "Checkpoint",
|
|
506
|
+
"type": "string"
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
"required": [
|
|
510
|
+
"checkpoint"
|
|
511
|
+
],
|
|
512
|
+
"type": "object",
|
|
513
|
+
"title": "shiftwork_planArguments"
|
|
514
|
+
},
|
|
515
|
+
"outputSchema": {
|
|
516
|
+
"type": "object",
|
|
517
|
+
"additionalProperties": true,
|
|
518
|
+
"title": "shiftwork_planDictOutput"
|
|
519
|
+
}
|
|
452
520
|
}
|
|
453
521
|
}
|
|
454
522
|
}
|
|
@@ -332,7 +332,7 @@ async def test_a_raising_handler_names_the_type_and_leaks_no_argument_value(tmp_
|
|
|
332
332
|
|
|
333
333
|
@synchronous
|
|
334
334
|
async def test_no_free_text_argument_reaches_the_file(tmp_path):
|
|
335
|
-
"""
|
|
335
|
+
"""Nine of the fourteen tools take an unbounded string. None of it is on disk.
|
|
336
336
|
|
|
337
337
|
(The count is `eventlog.py`'s rule over `assets/tools/`: a `string` parameter with no
|
|
338
338
|
`enum` or `maxLength`, or an array of such.) `skill_audit`'s `root` is covered by
|
|
@@ -216,6 +216,10 @@ CORE_MODULES = (
|
|
|
216
216
|
"critique.py",
|
|
217
217
|
"evalrun.py",
|
|
218
218
|
"mcpserver.py",
|
|
219
|
+
# 2026-09-18, W3. `workplan.py` landed as a Layer 1 core module in W1 and its purity
|
|
220
|
+
# was asserted by its own docstring and nothing mechanical. MEASURED before adding it:
|
|
221
|
+
# a contract literal pasted into it left this file at `44 passed`.
|
|
222
|
+
"workplan.py",
|
|
219
223
|
)
|
|
220
224
|
LAYER_MODULES = ("contract.py", "profile.py")
|
|
221
225
|
FORBIDDEN_IMPORTS = (
|