bantamkit 0.36.1__tar.gz → 0.36.2__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.36.1 → bantamkit-0.36.2}/PKG-INFO +12 -7
- {bantamkit-0.36.1 → bantamkit-0.36.2}/README.md +11 -6
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/skills/memory.md +1 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/memory_recall.json +9 -1
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/memory_save.json +17 -1
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/__init__.py +1 -1
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/hookadapter.py +159 -52
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/mcpserver.py +24 -16
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/__main__.py +123 -2
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/component.py +26 -5
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/store.py +224 -2
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/served-tool-surface.json +26 -2
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_eventlog.py +1 -1
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_hookadapter.py +311 -3
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcpserver.py +1 -1
- bantamkit-0.36.2/tests/test_memory_lifecycle.py +293 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_unknown_arguments.py +1 -1
- {bantamkit-0.36.1 → bantamkit-0.36.2}/.gitignore +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/contracts/default.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/manifest.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/HISTORY.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/README.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/fixtures/.gitkeep +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/fixtures/catalog.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/perturbations/task-completion.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/.gitkeep +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/extract-contact.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/extract-invoice.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/extract-order.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/extract-schedule.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/extract-versions.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-db-port.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-deploy.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-oncall.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/recall-owner.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-compare.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/evals/tasks/shop-total.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/pricing/default.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/profiles/default.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/profiles/patient.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/rubrics/.gitkeep +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/rubrics/code-quality.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/rubrics/grounded-completion.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/rubrics/task-completion.yaml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/schemas/shiftwork-checkpoint.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/skills/.gitkeep +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/skills/file-graph.md +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/.gitkeep +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/bantamkit_read.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/bantamkit_status.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/build_identity.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/document_list.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/document_read.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/file_graph.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/memory_compact.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/memory_dream.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/repo_map.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/shiftwork_clock_in.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/shiftwork_clock_out.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/shiftwork_plan.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/shiftwork_status.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/skill_audit.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/token_ledger.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/validate_json.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/_assets/tools/work_plan.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/hatch_build.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/pyproject.toml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/agent.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/assets.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/budget.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/client.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/contract.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/criticreplay.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/critique.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/docmanifest.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/docread.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/evalrun.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/eventlog.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/filegraph.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/hostinstall.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/loopguard.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/mcpreport.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/mcpstdio.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/__init__.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/divergence.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/dream.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/memory/layers.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/pdfread.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/pricing.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/profile.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/repomap.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/selfupdate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/shiftwork.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/skillaudit.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/statusline.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/structured.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/textutil.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/tokenledger.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/updatecheck.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/src/bantamkit/workplan.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/cli_exit_status_probe.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/conftest.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/bad-crc.docx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/charref-4301-digits.html +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/charset-table.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/compression-method-9.docx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/corrupt-deflate.docx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/encrypted-member.docx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/encrypted-mimetype.odt +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/internal-dtd-entity.docx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/rfc2231-charset.eml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/rfc822-nested-twice.eml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/docread/x-uuencode.eml +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/f8404ab-perturbation-baseline.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/data/platform-assumption-baseline.json +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/docread_fixtures.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/perturbation_baseline_harness.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/rbp16_effect_probe.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/rbp18_payload_probe.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_adapter.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_agent.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_amendguard.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_assets.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_bantamkit_gitignore.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_bantamkit_read_tool.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_budget.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_build_identity.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_cli_oneshot.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_client.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_compaction_corpus_survey.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_conformance.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_conformance_harness_resilience.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_conformance_suite_table_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_contract_fanout.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_contract_patterns.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_contract_refs.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_criticreplay.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_critique.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_doc_commands_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_docread.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_docread_ceilings.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_document_manifest_parity.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_document_setup.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_document_tasks.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_document_tools.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_encoding_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_evalrun.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_field_program_gates.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_field_programs.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_filegraph.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_hostinstall.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_hostinstall_hooks.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_install_shape.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_ladder_statistics.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_launcher_which.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_layers.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_loopguard.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcp_endpoint.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcp_request_guard.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcpdrift.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcpreport.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mcpstdio_frames.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_compact_tool.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_component.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_divergence.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_dream.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_layers.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_store_tripwire.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_memory_strings.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_mutmatrix.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_nativeexport.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_newline_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_packaging.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_pdfread.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_pinharness_ledger.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_platform_assumption_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_pricing.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_readme_separation.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_repo_map_tool.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_repomap.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_selfupdate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_served_tool_count_records.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_shiftwork.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_skillaudit.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_status_surface.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_statusline.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_structured.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_thread_exception_gate.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_tokenledger.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_tool_annotations.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_tool_manifest.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_updatecheck.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_version_agreement.py +0 -0
- {bantamkit-0.36.1 → bantamkit-0.36.2}/tests/test_workplan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: bantamkit
|
|
3
|
-
Version: 0.36.
|
|
3
|
+
Version: 0.36.2
|
|
4
4
|
Summary: Persistent memory for coding agents: an MCP server for Claude Code, Claude Desktop, Cursor and VS Code Copilot, 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
|
|
@@ -74,8 +74,9 @@ Then restart the host (in Claude Code, open a new session) and ask the agent to
|
|
|
74
74
|
- **Offline after one install.** `--install` records the venv's console script by absolute path,
|
|
75
75
|
so no launch needs the network or your shell's PATH.
|
|
76
76
|
- **Claude Code hooks, if you want them.** `--install-hooks` puts the memory index in front of
|
|
77
|
-
each new session, surfaces matching facts on each prompt,
|
|
78
|
-
|
|
77
|
+
each new session, surfaces matching facts on each prompt, mirrors each fact Claude Code writes
|
|
78
|
+
into its own auto-memory into the project store (one way, new writes only), and nudges a save at
|
|
79
|
+
the end of a long session that saved nothing
|
|
79
80
|
([docs/hooks.md](https://github.com/Ink01101011/bantamkit/blob/main/docs/hooks.md)).
|
|
80
81
|
- **More than memory.** JSON Schema validation, a token ledger read off the host's transcripts,
|
|
81
82
|
a skill-catalogue audit, and shift-work tools that plan and hand off multi-step jobs —
|
|
@@ -105,7 +106,7 @@ Then restart the host (in Claude Code, open a new session) and ask the agent to
|
|
|
105
106
|
| [What it serves](#-what-it-serves) | 14 tools, one prompt, two resource templates |
|
|
106
107
|
| [Requirements](#-requirements) | Python and dependencies |
|
|
107
108
|
| [The asset pack](#-the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
|
|
108
|
-
| [The operator CLI: `python -m bantamkit.memory`](#-the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
|
|
109
|
+
| [The operator CLI: `python -m bantamkit.memory`](#-the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore, list, candidates, merge |
|
|
109
110
|
| [Where the Python and Node servers differ](#-where-the-python-and-node-servers-differ) | One store; pdf/.doc/.rtf, CLI name, `build_id`, YAML only PyYAML reads |
|
|
110
111
|
| [Module API](#-module-api) | `import bantamkit`: the library half, measured from the wheel |
|
|
111
112
|
| [Development](#-development) | Clone, test, lint, conformance |
|
|
@@ -144,9 +145,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
144
145
|
one platform only), copy `wheels/` across, and install from it:
|
|
145
146
|
|
|
146
147
|
```bash
|
|
147
|
-
python -m pip download "bantamkit[mcp]==0.36.
|
|
148
|
+
python -m pip download "bantamkit[mcp]==0.36.2" -d wheels
|
|
148
149
|
python -m venv <env>
|
|
149
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.36.
|
|
150
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.36.2"
|
|
150
151
|
<env>/bin/bantamkit-mcp --install cursor
|
|
151
152
|
```
|
|
152
153
|
|
|
@@ -373,9 +374,13 @@ python -m bantamkit.memory compact # archive the stalest facts
|
|
|
373
374
|
python -m bantamkit.memory archived # list what compaction has moved out
|
|
374
375
|
python -m bantamkit.memory archive <name>
|
|
375
376
|
python -m bantamkit.memory restore <name>
|
|
377
|
+
python -m bantamkit.memory list [--type T] [--outcome error|success] [--status active|done]
|
|
378
|
+
python -m bantamkit.memory candidates [--older-than DAYS] [--apply]
|
|
379
|
+
python -m bantamkit.memory merge --names A B --name NEW --type T --description D --body TEXT [--apply]
|
|
376
380
|
```
|
|
377
381
|
|
|
378
|
-
`archive` moves a fact out without deleting it; `restore` brings it back by name.
|
|
382
|
+
`archive` moves a fact out without deleting it; `restore` brings it back by name. `candidates`
|
|
383
|
+
and `merge` are dry runs unless `--apply` is given.
|
|
379
384
|
Full reference: [docs/memory.md → The operator CLI](https://github.com/Ink01101011/bantamkit/blob/main/docs/memory.md#the-operator-cli).
|
|
380
385
|
|
|
381
386
|
## 🔀 Where the Python and Node servers differ
|
|
@@ -43,8 +43,9 @@ Then restart the host (in Claude Code, open a new session) and ask the agent to
|
|
|
43
43
|
- **Offline after one install.** `--install` records the venv's console script by absolute path,
|
|
44
44
|
so no launch needs the network or your shell's PATH.
|
|
45
45
|
- **Claude Code hooks, if you want them.** `--install-hooks` puts the memory index in front of
|
|
46
|
-
each new session, surfaces matching facts on each prompt,
|
|
47
|
-
|
|
46
|
+
each new session, surfaces matching facts on each prompt, mirrors each fact Claude Code writes
|
|
47
|
+
into its own auto-memory into the project store (one way, new writes only), and nudges a save at
|
|
48
|
+
the end of a long session that saved nothing
|
|
48
49
|
([docs/hooks.md](https://github.com/Ink01101011/bantamkit/blob/main/docs/hooks.md)).
|
|
49
50
|
- **More than memory.** JSON Schema validation, a token ledger read off the host's transcripts,
|
|
50
51
|
a skill-catalogue audit, and shift-work tools that plan and hand off multi-step jobs —
|
|
@@ -74,7 +75,7 @@ Then restart the host (in Claude Code, open a new session) and ask the agent to
|
|
|
74
75
|
| [What it serves](#-what-it-serves) | 14 tools, one prompt, two resource templates |
|
|
75
76
|
| [Requirements](#-requirements) | Python and dependencies |
|
|
76
77
|
| [The asset pack](#-the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
|
|
77
|
-
| [The operator CLI: `python -m bantamkit.memory`](#-the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
|
|
78
|
+
| [The operator CLI: `python -m bantamkit.memory`](#-the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore, list, candidates, merge |
|
|
78
79
|
| [Where the Python and Node servers differ](#-where-the-python-and-node-servers-differ) | One store; pdf/.doc/.rtf, CLI name, `build_id`, YAML only PyYAML reads |
|
|
79
80
|
| [Module API](#-module-api) | `import bantamkit`: the library half, measured from the wheel |
|
|
80
81
|
| [Development](#-development) | Clone, test, lint, conformance |
|
|
@@ -113,9 +114,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
113
114
|
one platform only), copy `wheels/` across, and install from it:
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
|
-
python -m pip download "bantamkit[mcp]==0.36.
|
|
117
|
+
python -m pip download "bantamkit[mcp]==0.36.2" -d wheels
|
|
117
118
|
python -m venv <env>
|
|
118
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.36.
|
|
119
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.36.2"
|
|
119
120
|
<env>/bin/bantamkit-mcp --install cursor
|
|
120
121
|
```
|
|
121
122
|
|
|
@@ -342,9 +343,13 @@ python -m bantamkit.memory compact # archive the stalest facts
|
|
|
342
343
|
python -m bantamkit.memory archived # list what compaction has moved out
|
|
343
344
|
python -m bantamkit.memory archive <name>
|
|
344
345
|
python -m bantamkit.memory restore <name>
|
|
346
|
+
python -m bantamkit.memory list [--type T] [--outcome error|success] [--status active|done]
|
|
347
|
+
python -m bantamkit.memory candidates [--older-than DAYS] [--apply]
|
|
348
|
+
python -m bantamkit.memory merge --names A B --name NEW --type T --description D --body TEXT [--apply]
|
|
345
349
|
```
|
|
346
350
|
|
|
347
|
-
`archive` moves a fact out without deleting it; `restore` brings it back by name.
|
|
351
|
+
`archive` moves a fact out without deleting it; `restore` brings it back by name. `candidates`
|
|
352
|
+
and `merge` are dry runs unless `--apply` is given.
|
|
348
353
|
Full reference: [docs/memory.md → The operator CLI](https://github.com/Ink01101011/bantamkit/blob/main/docs/memory.md#the-operator-cli).
|
|
349
354
|
|
|
350
355
|
## 🔀 Where the Python and Node servers differ
|
|
@@ -12,6 +12,7 @@ past-you would have used. Do this before acting, not after.
|
|
|
12
12
|
- The user corrects you or states a preference → type "feedback"
|
|
13
13
|
- A durable fact about the user → "user", about ongoing work → "project",
|
|
14
14
|
a URL/dashboard/ticket → "reference"
|
|
15
|
+
- Tag a lesson with outcome: error|success; close a project fact by saving it again with status: done.
|
|
15
16
|
|
|
16
17
|
## When NOT to save
|
|
17
18
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory_recall",
|
|
3
3
|
"title": "Recall memories",
|
|
4
|
-
"description": "Search persistent memory for facts a past session saved. Call it BEFORE starting a task that resembles past work, and when the user refers to something decided earlier; use memory_save to add a fact, and memory_compact or memory_dream only to tidy the store — this tool never changes what a fact says. Reads every layer in order — the project store, any read-only grants in .bantamkit/config.yaml, then the machine-wide profile — and returns the top word matches, never fewer than the store default of 3 when that many match; a query that is exactly a fact's name returns that fact alone. Why it is not read-only: every hit is appended to the usage ledger (~/.bantamkit/usage/<store-id>.jsonl) as a recall event — the staleness order memory_compact archives by — and a project fact file still in the 0.35.x format is rewritten once into the current one; both are best-effort and never fail a recall. A fact file that cannot be read is named in the reply, never silently skipped.",
|
|
4
|
+
"description": "Search persistent memory for facts a past session saved. Call it BEFORE starting a task that resembles past work, and when the user refers to something decided earlier; use memory_save to add a fact, and memory_compact or memory_dream only to tidy the store — this tool never changes what a fact says. Reads every layer in order — the project store, any read-only grants in .bantamkit/config.yaml, then the machine-wide profile — and returns the top word matches, never fewer than the store default of 3 when that many match; a query that is exactly a fact's name returns that fact alone. Why it is not read-only: every hit is appended to the usage ledger (~/.bantamkit/usage/<store-id>.jsonl) as a recall event — the staleness order memory_compact archives by — and a project fact file still in the 0.35.x format is rewritten once into the current one; both are best-effort and never fail a recall. A fact file that cannot be read is named in the reply, never silently skipped. Pass outcome: error to see only recorded failures.",
|
|
5
5
|
"surfaces": [
|
|
6
6
|
"agent",
|
|
7
7
|
"mcp"
|
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
"k": {
|
|
20
20
|
"type": "integer",
|
|
21
21
|
"description": "Facts wanted, clamped to 1..5. Raised to the store default of 3 when smaller; never lowered below it."
|
|
22
|
+
},
|
|
23
|
+
"outcome": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": [
|
|
26
|
+
"error",
|
|
27
|
+
"success"
|
|
28
|
+
],
|
|
29
|
+
"description": "Keep only facts saved with this outcome — `error` answers \"what failed before\", `success` \"what worked\". Facts with no outcome are dropped from the answer; omit it for the normal search."
|
|
22
30
|
}
|
|
23
31
|
},
|
|
24
32
|
"additionalProperties": false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory_save",
|
|
3
3
|
"title": "Save a memory",
|
|
4
|
-
"description": "Save ONE durable fact to persistent memory — a preference the user stated, a decision, the fix that worked — so a future session can recall it. Use it only for facts a future session needs; never for anything derivable from the repo, git history or docs, nor for one-off state of this conversation. To find a fact use memory_recall; to change one, save again under its EXACT name, which replaces it in place; to free room call memory_compact. Refused with nothing written, each with its own sentence: a description too close to an existing fact's (the reply names that fact — save under that SAME name or skip, never rename to force a copy), a spelling that normalises onto an existing name (`Deploy Command` onto `deploy-command`), and a save that would push the always-loaded index over its byte budget (the reply names the remedy). Every save lands in the writable project store (.bantamkit/memory), never in the profile or a read-only grant, and is appended to the usage ledger.",
|
|
4
|
+
"description": "Save ONE durable fact to persistent memory — a preference the user stated, a decision, the fix that worked — so a future session can recall it. Use it only for facts a future session needs; never for anything derivable from the repo, git history or docs, nor for one-off state of this conversation. To find a fact use memory_recall; to change one, save again under its EXACT name, which replaces it in place; to free room call memory_compact. Refused with nothing written, each with its own sentence: a description too close to an existing fact's (the reply names that fact — save under that SAME name or skip, never rename to force a copy), a spelling that normalises onto an existing name (`Deploy Command` onto `deploy-command`), and a save that would push the always-loaded index over its byte budget (the reply names the remedy). Every save lands in the writable project store (.bantamkit/memory), never in the profile or a read-only grant, and is appended to the usage ledger. Optional outcome (error|success) tags a lesson and status: done closes a project fact; nothing here archives — that is the operator's bantamkit-memory candidates/merge.",
|
|
5
5
|
"surfaces": [
|
|
6
6
|
"agent",
|
|
7
7
|
"mcp"
|
|
@@ -44,6 +44,22 @@
|
|
|
44
44
|
"type": "string"
|
|
45
45
|
},
|
|
46
46
|
"description": "names of related memories"
|
|
47
|
+
},
|
|
48
|
+
"outcome": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"enum": [
|
|
51
|
+
"error",
|
|
52
|
+
"success"
|
|
53
|
+
],
|
|
54
|
+
"description": "What the lesson records: `error` (a failure to avoid) or `success` (a way that worked). Only on feedback and project facts; refused on user and reference. Read back by `bantamkit-memory list --outcome` and by memory_recall's `outcome` filter."
|
|
55
|
+
},
|
|
56
|
+
"status": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": [
|
|
59
|
+
"active",
|
|
60
|
+
"done"
|
|
61
|
+
],
|
|
62
|
+
"description": "Lifecycle of a project fact. Save an existing name with `done` when the work it describes has shipped; `bantamkit-memory candidates` lists done facts for the operator to archive. Absent means active; `active` is never written."
|
|
47
63
|
}
|
|
48
64
|
},
|
|
49
65
|
"additionalProperties": false
|
|
@@ -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.36.
|
|
32
|
+
__version__ = "0.36.2"
|
|
@@ -73,9 +73,12 @@ from datetime import UTC, datetime
|
|
|
73
73
|
from pathlib import Path
|
|
74
74
|
from typing import Any
|
|
75
75
|
|
|
76
|
+
import yaml
|
|
77
|
+
|
|
78
|
+
from .client import BantamError
|
|
76
79
|
from .memory.component import Memory
|
|
77
80
|
from .memory.layers import discover_project_store, resolve_project_store
|
|
78
|
-
from .memory.store import DURABLE_TYPES, Fact, MemoryStore, tokens
|
|
81
|
+
from .memory.store import DURABLE_TYPES, NAME_MAX_CHARS, Fact, MemoryStore, tokens
|
|
79
82
|
|
|
80
83
|
# The process clock, for the `ms` on every log line. `time.time()` and not `monotonic`,
|
|
81
84
|
# because the Node module reads `Date.now()` here and the two logs are read side by side.
|
|
@@ -1728,21 +1731,33 @@ def _post_save(run: HookRun, payload: dict[str, Any]) -> None:
|
|
|
1728
1731
|
ledger["saved"] = int(ledger.get("saved") or 0) + 1
|
|
1729
1732
|
_write_ledger(run, payload.get("session_id"), ledger)
|
|
1730
1733
|
cwd = str(payload.get("cwd") or os.getcwd())
|
|
1731
|
-
configured,
|
|
1732
|
-
budget_source = "configured" if configured is not None else "default"
|
|
1733
|
-
budget_scope = scope if configured is not None else None
|
|
1734
|
+
configured, _scope = _configured_index_budget(run, cwd)
|
|
1734
1735
|
m = (
|
|
1735
1736
|
Memory.layered(cwd)
|
|
1736
1737
|
if configured is None
|
|
1737
1738
|
else Memory.layered(cwd, index_budget=_budget_value(configured))
|
|
1738
1739
|
)
|
|
1740
|
+
_compact_if_needed(run, payload, cwd, m, {"action": "saved"})
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
def _compact_if_needed(
|
|
1744
|
+
run: HookRun, payload: dict[str, Any], cwd: str, m: Memory, record: dict[str, Any]
|
|
1745
|
+
) -> None:
|
|
1746
|
+
"""The auto-compact tail shared by `_post_save` and the native mirror (#228).
|
|
1747
|
+
|
|
1748
|
+
`record` already carries `action` (and for the mirror `file`, `name`, `nativeBranch`);
|
|
1749
|
+
it is logged as is when the index is under the band.
|
|
1750
|
+
"""
|
|
1751
|
+
configured, scope = _configured_index_budget(run, cwd)
|
|
1752
|
+
budget_source = "configured" if configured is not None else "default"
|
|
1753
|
+
budget_scope = scope if configured is not None else None
|
|
1739
1754
|
size, budget = m.index_accounting()
|
|
1740
1755
|
if size is None or size < COMPACT_AT * budget:
|
|
1741
1756
|
_log(
|
|
1742
1757
|
run,
|
|
1743
1758
|
{
|
|
1759
|
+
**record,
|
|
1744
1760
|
"event": "PostToolUse",
|
|
1745
|
-
"action": "saved",
|
|
1746
1761
|
"bytes": size,
|
|
1747
1762
|
"budget": budget,
|
|
1748
1763
|
"budgetSource": budget_source,
|
|
@@ -2282,6 +2297,142 @@ def _maybe_dream(run: HookRun, payload: dict[str, Any]) -> None:
|
|
|
2282
2297
|
)
|
|
2283
2298
|
|
|
2284
2299
|
|
|
2300
|
+
# ---------------------------------------------- PostToolUse[Write|Edit|MultiEdit] -> mirror
|
|
2301
|
+
# #228: a fact the host writes into its native memory directory is saved, one way, into the
|
|
2302
|
+
# project store. One file per call (the one the tool just wrote); never a scan, never a write
|
|
2303
|
+
# into the native directory, never a read of MEMORY.md.
|
|
2304
|
+
|
|
2305
|
+
MIRROR_TOOLS = frozenset({"Write", "Edit", "MultiEdit"})
|
|
2306
|
+
#: A `.md` path with a `memory` directory segment (the old Stop-nudge shape).
|
|
2307
|
+
_MEMORY_SEGMENT = re.compile(r"[\\/]memory[\\/].*\.md\Z", re.DOTALL)
|
|
2308
|
+
_WIKILINK = re.compile(r"\[\[([^\]]+)\]\]")
|
|
2309
|
+
_MIRROR_NAME = re.compile(r"^[a-z0-9][a-z0-9-]*\Z")
|
|
2310
|
+
_MIRROR_TYPES = ("user", "feedback", "project", "reference")
|
|
2311
|
+
|
|
2312
|
+
|
|
2313
|
+
def _mirror_name(value: Any) -> str | None:
|
|
2314
|
+
name = str(value).strip().lower().replace(".", "-").replace("_", "-")
|
|
2315
|
+
if not _MIRROR_NAME.match(name) or len(name) > NAME_MAX_CHARS:
|
|
2316
|
+
return None
|
|
2317
|
+
return name
|
|
2318
|
+
|
|
2319
|
+
|
|
2320
|
+
def _mirror_links(meta: dict[str, Any], body: str) -> list[str]:
|
|
2321
|
+
raw: list[str] = []
|
|
2322
|
+
listed = meta.get("links")
|
|
2323
|
+
if isinstance(listed, list):
|
|
2324
|
+
raw.extend(str(x) for x in listed)
|
|
2325
|
+
raw.extend(_WIKILINK.findall(body))
|
|
2326
|
+
out: list[str] = []
|
|
2327
|
+
for item in raw:
|
|
2328
|
+
name = _mirror_name(item)
|
|
2329
|
+
if name is not None and name not in out:
|
|
2330
|
+
out.append(name)
|
|
2331
|
+
return out
|
|
2332
|
+
|
|
2333
|
+
|
|
2334
|
+
def _parse_native_fact(text: str) -> tuple[dict[str, Any], str] | None:
|
|
2335
|
+
try:
|
|
2336
|
+
_, front, body = text.split("---\n", 2)
|
|
2337
|
+
meta = yaml.safe_load(front)
|
|
2338
|
+
except (ValueError, yaml.YAMLError):
|
|
2339
|
+
return None
|
|
2340
|
+
# NO KEYS IS NO FACT (J74-4): `{}` is a dict to PyYAML and an empty or comment-only
|
|
2341
|
+
# frontmatter is `None`; the port refuses `{}` as #120's empty flow mapping. One rule,
|
|
2342
|
+
# spelled the same on both sides, so all three are a `frontmatter` skip.
|
|
2343
|
+
if not isinstance(meta, dict) or not meta:
|
|
2344
|
+
return None
|
|
2345
|
+
return meta, body
|
|
2346
|
+
|
|
2347
|
+
|
|
2348
|
+
def _mirror_native_write(run: HookRun, payload: dict[str, Any]) -> None:
|
|
2349
|
+
ti = payload.get("tool_input")
|
|
2350
|
+
fp = ti.get("file_path") if isinstance(ti, dict) else None
|
|
2351
|
+
if not isinstance(fp, str) or not fp:
|
|
2352
|
+
return
|
|
2353
|
+
native = _resolve_native_memory(run, payload)
|
|
2354
|
+
base: dict[str, Any] = {"event": "PostToolUse", "file": fp, "nativeBranch": native.branch}
|
|
2355
|
+
|
|
2356
|
+
def skip(reason: str, **more: Any) -> None:
|
|
2357
|
+
_log(run, {"action": "mirror-skipped", **base, **more, "reason": reason})
|
|
2358
|
+
|
|
2359
|
+
inside = False
|
|
2360
|
+
if native.dir is not None and fp.endswith(".md") and os.path.basename(fp) != NATIVE_INDEX:
|
|
2361
|
+
root = os.path.realpath(native.dir)
|
|
2362
|
+
parent = os.path.realpath(os.path.dirname(fp))
|
|
2363
|
+
inside = parent == root or (parent + os.sep).startswith(root.rstrip(os.sep) + os.sep)
|
|
2364
|
+
if not inside:
|
|
2365
|
+
if (
|
|
2366
|
+
fp.endswith(".md")
|
|
2367
|
+
and _MEMORY_SEGMENT.search(fp)
|
|
2368
|
+
and os.path.basename(fp) != NATIVE_INDEX
|
|
2369
|
+
):
|
|
2370
|
+
skip("not-native")
|
|
2371
|
+
return
|
|
2372
|
+
try:
|
|
2373
|
+
with open(fp, encoding="utf-8", errors="replace") as fh:
|
|
2374
|
+
text = fh.read()
|
|
2375
|
+
except OSError:
|
|
2376
|
+
skip("frontmatter")
|
|
2377
|
+
return
|
|
2378
|
+
parsed = _parse_native_fact(text)
|
|
2379
|
+
if parsed is None:
|
|
2380
|
+
skip("frontmatter")
|
|
2381
|
+
return
|
|
2382
|
+
meta, raw_body = parsed
|
|
2383
|
+
nested = meta.get("metadata") if isinstance(meta.get("metadata"), dict) else {}
|
|
2384
|
+
if nested.get("source") == "bantamkit":
|
|
2385
|
+
skip("export-stub")
|
|
2386
|
+
return
|
|
2387
|
+
source = meta.get("name")
|
|
2388
|
+
if not (isinstance(source, str) and source):
|
|
2389
|
+
source = os.path.basename(fp)[: -len(".md")]
|
|
2390
|
+
name = _mirror_name(source)
|
|
2391
|
+
if name is None:
|
|
2392
|
+
skip("name")
|
|
2393
|
+
return
|
|
2394
|
+
fact_type = meta.get("type") if isinstance(meta.get("type"), str) else nested.get("type")
|
|
2395
|
+
if fact_type not in _MIRROR_TYPES:
|
|
2396
|
+
fact_type = "project"
|
|
2397
|
+
# A list or a mapping is not a description (J74-4): `str()` and `String()` spell one
|
|
2398
|
+
# differently, so neither side may turn it into text.
|
|
2399
|
+
raw_description = meta.get("description")
|
|
2400
|
+
if isinstance(raw_description, (list, dict)):
|
|
2401
|
+
raw_description = None
|
|
2402
|
+
description = _collapse_whitespace(raw_description or "")
|
|
2403
|
+
if not description:
|
|
2404
|
+
skip("description", name=name)
|
|
2405
|
+
return
|
|
2406
|
+
body = raw_body.strip()
|
|
2407
|
+
if not body:
|
|
2408
|
+
skip("body", name=name)
|
|
2409
|
+
return
|
|
2410
|
+
links = _mirror_links(meta, body)
|
|
2411
|
+
cwd = str(payload.get("cwd") or os.getcwd())
|
|
2412
|
+
configured, _scope = _configured_index_budget(run, cwd)
|
|
2413
|
+
m = (
|
|
2414
|
+
Memory.layered(cwd)
|
|
2415
|
+
if configured is None
|
|
2416
|
+
else Memory.layered(cwd, index_budget=_budget_value(configured))
|
|
2417
|
+
)
|
|
2418
|
+
try:
|
|
2419
|
+
result = m.store.save(fact_type, name, description, body, tuple(links))
|
|
2420
|
+
except BantamError as e:
|
|
2421
|
+
_log(run, {"action": "mirror-refused", **base, "name": name, "reason": str(e)})
|
|
2422
|
+
return
|
|
2423
|
+
ledger = _read_ledger(run, payload.get("session_id"))
|
|
2424
|
+
ledger["saved"] = int(ledger.get("saved") or 0) + 1
|
|
2425
|
+
_write_ledger(run, payload.get("session_id"), ledger)
|
|
2426
|
+
action = "mirrored" if result.status == "saved" else "mirror-duplicate"
|
|
2427
|
+
_compact_if_needed(
|
|
2428
|
+
run,
|
|
2429
|
+
payload,
|
|
2430
|
+
cwd,
|
|
2431
|
+
m,
|
|
2432
|
+
{"action": action, "file": fp, "name": name, "nativeBranch": native.branch},
|
|
2433
|
+
)
|
|
2434
|
+
|
|
2435
|
+
|
|
2285
2436
|
# -------------------------------------------------------------------------- Stop
|
|
2286
2437
|
# The experience collector. Once per session, when the session did real work and nothing
|
|
2287
2438
|
# durable was written, hand the turn back with one instruction. The host's `type:prompt`
|
|
@@ -2289,48 +2440,6 @@ def _maybe_dream(run: HookRun, payload: dict[str, Any]) -> None:
|
|
|
2289
2440
|
|
|
2290
2441
|
_TOOL_USE = re.compile(r'"type":\s*"tool_use"')
|
|
2291
2442
|
_SAVE_CALL = re.compile(r'"name":\s*"mcp__bantamkit__memory_save"')
|
|
2292
|
-
#: The native write tools whose `file_path` can put a fact into a `memory/` directory.
|
|
2293
|
-
_SAVE_WRITE_TOOLS = frozenset({"Write", "Edit", "MultiEdit"})
|
|
2294
|
-
#: The path shape of a native memory write: a `.md` under any `memory/` directory. The class
|
|
2295
|
-
#: is backslash-or-slash, spelled as a module constant because a backslash inside an f-string
|
|
2296
|
-
#: expression is a syntax error before CPython 3.12 and this package supports 3.11.
|
|
2297
|
-
_SAVE_WRITE_PATH = re.compile(r"[\\/]memory[\\/].*\.md\Z", re.DOTALL)
|
|
2298
|
-
#: A line can only hold a native write if it names one of the tools; the rest are not parsed.
|
|
2299
|
-
_SAVE_LINE_HINT = re.compile(r'"name":\s*"(?:Write|Edit|MultiEdit)"')
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
def _transcript_wrote_memory(text: str) -> bool:
|
|
2303
|
-
"""Did the transcript WRITE a native memory file (job65, #169)? Only a `tool_use` counts.
|
|
2304
|
-
|
|
2305
|
-
A Write/Edit/MultiEdit whose `file_path` is a `.md` under a `memory/` directory. Before
|
|
2306
|
-
this a regex over the whole text took ANY `"file_path"` under `memory/` -- a Read of
|
|
2307
|
-
`docs/memory/notes.md` included -- as a save, and silenced the nudge. (A `memory_save`
|
|
2308
|
-
call is still recognised by its tool name, as it always was; that half was never the
|
|
2309
|
-
defect.) Lines are prefiltered on the tool names and only those parsed; a line that does
|
|
2310
|
-
not parse is not a save.
|
|
2311
|
-
"""
|
|
2312
|
-
for line in text.split("\n"):
|
|
2313
|
-
if not _SAVE_LINE_HINT.search(line):
|
|
2314
|
-
continue
|
|
2315
|
-
try:
|
|
2316
|
-
record = json.loads(line)
|
|
2317
|
-
except ValueError:
|
|
2318
|
-
continue
|
|
2319
|
-
msg = record.get("message") if isinstance(record, dict) else None
|
|
2320
|
-
content = msg.get("content") if isinstance(msg, dict) else None
|
|
2321
|
-
if not isinstance(content, list):
|
|
2322
|
-
continue
|
|
2323
|
-
for block in content:
|
|
2324
|
-
if not isinstance(block, dict) or block.get("type") != "tool_use":
|
|
2325
|
-
continue
|
|
2326
|
-
if block.get("name") not in _SAVE_WRITE_TOOLS:
|
|
2327
|
-
continue
|
|
2328
|
-
ti = block.get("input")
|
|
2329
|
-
fp = ti.get("file_path") if isinstance(ti, dict) else None
|
|
2330
|
-
if isinstance(fp, str) and _SAVE_WRITE_PATH.search(fp):
|
|
2331
|
-
return True
|
|
2332
|
-
return False
|
|
2333
|
-
|
|
2334
2443
|
|
|
2335
2444
|
def _stop(run: HookRun, payload: dict[str, Any]) -> None:
|
|
2336
2445
|
if payload.get("stop_hook_active"):
|
|
@@ -2346,11 +2455,7 @@ def _stop(run: HookRun, payload: dict[str, Any]) -> None:
|
|
|
2346
2455
|
except OSError:
|
|
2347
2456
|
return
|
|
2348
2457
|
tool_uses = len(_TOOL_USE.findall(text))
|
|
2349
|
-
saved = bool(
|
|
2350
|
-
int(ledger.get("saved") or 0) > 0
|
|
2351
|
-
or _SAVE_CALL.search(text)
|
|
2352
|
-
or _transcript_wrote_memory(text)
|
|
2353
|
-
)
|
|
2458
|
+
saved = bool(int(ledger.get("saved") or 0) > 0 or _SAVE_CALL.search(text))
|
|
2354
2459
|
if tool_uses < STOP_NUDGE_MIN_TOOL_CALLS or saved:
|
|
2355
2460
|
_log(run, {"event": "Stop", "action": "pass", "toolUses": tool_uses, "saved": saved})
|
|
2356
2461
|
return
|
|
@@ -2435,6 +2540,8 @@ def run_hook(stdin: Any = None) -> None:
|
|
|
2435
2540
|
_append_usage_event(run, payload)
|
|
2436
2541
|
if payload.get("tool_name") == "mcp__bantamkit__memory_save":
|
|
2437
2542
|
_post_save(run, payload)
|
|
2543
|
+
elif payload.get("tool_name") in MIRROR_TOOLS:
|
|
2544
|
+
_mirror_native_write(run, payload)
|
|
2438
2545
|
return
|
|
2439
2546
|
if event == "PreCompact":
|
|
2440
2547
|
_pre_compact(run, payload)
|
|
@@ -1464,10 +1464,18 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1464
1464
|
return f'{document[:-2]},\n "bantamkit_degraded": {key}\n}}'
|
|
1465
1465
|
|
|
1466
1466
|
def memory_save(
|
|
1467
|
-
type: str,
|
|
1467
|
+
type: str,
|
|
1468
|
+
name: str,
|
|
1469
|
+
description: str,
|
|
1470
|
+
body: str,
|
|
1471
|
+
links: list[str] | None = None,
|
|
1472
|
+
outcome: str | None = None,
|
|
1473
|
+
status: str | None = None,
|
|
1468
1474
|
) -> str:
|
|
1469
1475
|
with _record_raise(log, "memory_save"):
|
|
1470
|
-
|
|
1476
|
+
outcome_ = memory.save_outcome(
|
|
1477
|
+
type, name, description, body, links, outcome=outcome, status=status
|
|
1478
|
+
)
|
|
1471
1479
|
if log.enabled:
|
|
1472
1480
|
# Only when a log is actually on: `index_accounting` re-parses `facts/`, and
|
|
1473
1481
|
# a diagnostic must not put that on the path of an operator who did not ask
|
|
@@ -1477,10 +1485,10 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1477
1485
|
detail: dict[str, Any] = {"budget": budget}
|
|
1478
1486
|
if index_bytes is not None:
|
|
1479
1487
|
detail["index_bytes"] = index_bytes
|
|
1480
|
-
log.record("memory_save",
|
|
1481
|
-
return _noted(
|
|
1488
|
+
log.record("memory_save", outcome_.status, detail)
|
|
1489
|
+
return _noted(outcome_.reply)
|
|
1482
1490
|
|
|
1483
|
-
def memory_recall(query: str, k: int | None = None) -> str:
|
|
1491
|
+
def memory_recall(query: str, k: int | None = None, outcome: str | None = None) -> str:
|
|
1484
1492
|
if k is not None:
|
|
1485
1493
|
# Clamped, not refused, and the manifest says so: `assets/tools/memory_recall.json`
|
|
1486
1494
|
# used to advertise `minimum: 1, maximum: 5` on `k` while this line and the
|
|
@@ -1490,19 +1498,19 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1490
1498
|
# answer for a stricter one. The description now states the clamp.
|
|
1491
1499
|
k = max(1, min(k, 5))
|
|
1492
1500
|
with _record_raise(log, "memory_recall"):
|
|
1493
|
-
|
|
1501
|
+
outcome_ = memory.recall_outcome(query, k, outcome=outcome)
|
|
1494
1502
|
detail = {
|
|
1495
|
-
"budget":
|
|
1496
|
-
"candidates":
|
|
1497
|
-
"layers":
|
|
1498
|
-
"reached":
|
|
1499
|
-
"returned":
|
|
1500
|
-
"unreadable":
|
|
1503
|
+
"budget": outcome_.budget,
|
|
1504
|
+
"candidates": outcome_.candidates,
|
|
1505
|
+
"layers": outcome_.layers,
|
|
1506
|
+
"reached": outcome_.reached,
|
|
1507
|
+
"returned": outcome_.returned,
|
|
1508
|
+
"unreadable": outcome_.unreadable,
|
|
1501
1509
|
}
|
|
1502
|
-
if
|
|
1503
|
-
detail["source"] =
|
|
1504
|
-
log.record("memory_recall",
|
|
1505
|
-
return _noted(
|
|
1510
|
+
if outcome_.source is not None:
|
|
1511
|
+
detail["source"] = outcome_.source
|
|
1512
|
+
log.record("memory_recall", outcome_.status, detail)
|
|
1513
|
+
return _noted(outcome_.reply)
|
|
1506
1514
|
|
|
1507
1515
|
def memory_compact(reserve: int | None = None) -> str:
|
|
1508
1516
|
"""The model's half of compaction; the hook (`docs/hooks.md`) is the automatic half.
|