bantamkit 0.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. bantamkit/__init__.py +32 -0
  2. bantamkit/agent.py +458 -0
  3. bantamkit/assets/contracts/default.yaml +90 -0
  4. bantamkit/assets/evals/devteam/manifest.yaml +351 -0
  5. bantamkit/assets/evals/devteam/repo/HISTORY.md +18 -0
  6. bantamkit/assets/evals/devteam/repo/README.md +12 -0
  7. bantamkit/assets/evals/devteam/repo/docs/architecture.md +17 -0
  8. bantamkit/assets/evals/devteam/repo/docs/runbook.md +10 -0
  9. bantamkit/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
  10. bantamkit/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
  11. bantamkit/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
  12. bantamkit/assets/evals/devteam/repo/src/ledger/config.py +35 -0
  13. bantamkit/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
  14. bantamkit/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
  15. bantamkit/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
  16. bantamkit/assets/evals/devteam/repo/src/ledger/report.py +9 -0
  17. bantamkit/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
  18. bantamkit/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
  19. bantamkit/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
  20. bantamkit/assets/evals/devteam/repo/tests/test_posting.py +13 -0
  21. bantamkit/assets/evals/devteam/repo/tests/test_settle.py +9 -0
  22. bantamkit/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
  23. bantamkit/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
  24. bantamkit/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
  25. bantamkit/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
  26. bantamkit/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
  27. bantamkit/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
  28. bantamkit/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
  29. bantamkit/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
  30. bantamkit/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
  31. bantamkit/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
  32. bantamkit/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
  33. bantamkit/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
  34. bantamkit/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
  35. bantamkit/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
  36. bantamkit/assets/evals/document/tasks/doc-small-137.yaml +37 -0
  37. bantamkit/assets/evals/document/tasks/doc-small-261.yaml +37 -0
  38. bantamkit/assets/evals/document/tasks/doc-small-388.yaml +37 -0
  39. bantamkit/assets/evals/fixtures/.gitkeep +0 -0
  40. bantamkit/assets/evals/fixtures/catalog.json +6 -0
  41. bantamkit/assets/evals/perturbations/task-completion.yaml +576 -0
  42. bantamkit/assets/evals/tasks/.gitkeep +0 -0
  43. bantamkit/assets/evals/tasks/extract-contact.yaml +14 -0
  44. bantamkit/assets/evals/tasks/extract-invoice.yaml +14 -0
  45. bantamkit/assets/evals/tasks/extract-order.yaml +15 -0
  46. bantamkit/assets/evals/tasks/extract-schedule.yaml +14 -0
  47. bantamkit/assets/evals/tasks/extract-versions.yaml +17 -0
  48. bantamkit/assets/evals/tasks/nav-prod-port.yaml +84 -0
  49. bantamkit/assets/evals/tasks/nav-release-bundle.yaml +87 -0
  50. bantamkit/assets/evals/tasks/recall-audit-retention.yaml +17 -0
  51. bantamkit/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
  52. bantamkit/assets/evals/tasks/recall-db-port.yaml +17 -0
  53. bantamkit/assets/evals/tasks/recall-deploy.yaml +13 -0
  54. bantamkit/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
  55. bantamkit/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
  56. bantamkit/assets/evals/tasks/recall-oncall.yaml +13 -0
  57. bantamkit/assets/evals/tasks/recall-org-quota.yaml +18 -0
  58. bantamkit/assets/evals/tasks/recall-owner.yaml +13 -0
  59. bantamkit/assets/evals/tasks/shop-basket-total.yaml +10 -0
  60. bantamkit/assets/evals/tasks/shop-cheapest.yaml +9 -0
  61. bantamkit/assets/evals/tasks/shop-compare.yaml +9 -0
  62. bantamkit/assets/evals/tasks/shop-gadget-value.yaml +9 -0
  63. bantamkit/assets/evals/tasks/shop-stock-total.yaml +9 -0
  64. bantamkit/assets/evals/tasks/shop-total.yaml +9 -0
  65. bantamkit/assets/profiles/default.yaml +31 -0
  66. bantamkit/assets/profiles/patient.yaml +31 -0
  67. bantamkit/assets/rubrics/.gitkeep +0 -0
  68. bantamkit/assets/rubrics/code-quality.yaml +20 -0
  69. bantamkit/assets/rubrics/grounded-completion.yaml +37 -0
  70. bantamkit/assets/rubrics/task-completion.yaml +28 -0
  71. bantamkit/assets/schemas/shiftwork-checkpoint.json +188 -0
  72. bantamkit/assets/skills/.gitkeep +0 -0
  73. bantamkit/assets/skills/file-graph.md +7 -0
  74. bantamkit/assets/skills/memory.md +35 -0
  75. bantamkit/assets/tools/.gitkeep +0 -0
  76. bantamkit/assets/tools/bantamkit_read.json +48 -0
  77. bantamkit/assets/tools/bantamkit_status.json +25 -0
  78. bantamkit/assets/tools/build_identity.json +17 -0
  79. bantamkit/assets/tools/document_list.json +12 -0
  80. bantamkit/assets/tools/document_read.json +31 -0
  81. bantamkit/assets/tools/file_graph.json +12 -0
  82. bantamkit/assets/tools/memory_compact.json +31 -0
  83. bantamkit/assets/tools/memory_recall.json +38 -0
  84. bantamkit/assets/tools/memory_save.json +61 -0
  85. bantamkit/assets/tools/shiftwork_clock_in.json +25 -0
  86. bantamkit/assets/tools/shiftwork_clock_out.json +60 -0
  87. bantamkit/assets/tools/shiftwork_status.json +25 -0
  88. bantamkit/assets/tools/skill_audit.json +70 -0
  89. bantamkit/assets/tools/validate_json.json +31 -0
  90. bantamkit/assets.py +67 -0
  91. bantamkit/budget.py +114 -0
  92. bantamkit/client.py +329 -0
  93. bantamkit/contract.py +522 -0
  94. bantamkit/criticreplay.py +3241 -0
  95. bantamkit/critique.py +301 -0
  96. bantamkit/docread.py +1744 -0
  97. bantamkit/evalrun.py +2003 -0
  98. bantamkit/eventlog.py +282 -0
  99. bantamkit/filegraph.py +218 -0
  100. bantamkit/loopguard.py +101 -0
  101. bantamkit/mcpreport.py +763 -0
  102. bantamkit/mcpserver.py +1334 -0
  103. bantamkit/memory/__init__.py +28 -0
  104. bantamkit/memory/__main__.py +291 -0
  105. bantamkit/memory/component.py +569 -0
  106. bantamkit/memory/divergence.py +744 -0
  107. bantamkit/memory/layers.py +257 -0
  108. bantamkit/memory/store.py +940 -0
  109. bantamkit/pdfread.py +1402 -0
  110. bantamkit/profile.py +46 -0
  111. bantamkit/shiftwork.py +212 -0
  112. bantamkit/skillaudit.py +853 -0
  113. bantamkit/statusline.py +313 -0
  114. bantamkit/structured.py +125 -0
  115. bantamkit/textutil.py +30 -0
  116. bantamkit-0.27.0.dist-info/METADATA +207 -0
  117. bantamkit-0.27.0.dist-info/RECORD +119 -0
  118. bantamkit-0.27.0.dist-info/WHEEL +4 -0
  119. bantamkit-0.27.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,28 @@
1
+ from bantamkit.memory.component import CompactOutcome, Memory, RecallOutcome, SaveOutcome
2
+ from bantamkit.memory.divergence import (
3
+ BodyDiff,
4
+ DivergenceReport,
5
+ StoredFact,
6
+ UnparseableFact,
7
+ bantamkit_store_root,
8
+ compare_stores,
9
+ native_store_root,
10
+ parse_fact,
11
+ read_store,
12
+ )
13
+ from bantamkit.memory.layers import (
14
+ MEMORY_DIR_ENV,
15
+ StoreBinding,
16
+ discover_project_store,
17
+ resolve_project_store,
18
+ )
19
+ from bantamkit.memory.store import (
20
+ DEFAULT_INDEX_BUDGET,
21
+ ArchivedFact,
22
+ CompactResult,
23
+ Fact,
24
+ MemoryBudgetExceeded,
25
+ MemoryStore,
26
+ MemoryValidationError,
27
+ SaveResult,
28
+ )
@@ -0,0 +1,291 @@
1
+ """The operator's half of the lifecycle position: `python -m bantamkit.memory`.
2
+
3
+ `docs/memory.md` states, as a design decision, that `lint`, `archived` and
4
+ `restore` are **not** agent tools — "lifecycle is an operator decision, not a
5
+ model decision" — and the eleven-tool surface holds to it; `compact` is the one
6
+ exception since job42 (`memory_compact`, the on-refusal path the model reaches
7
+ itself). That position is only coherent if the operator can actually make the
8
+ decision. Measured
9
+ 2026-08-21, they could not: `index_budget` appeared on no argument parser, and
10
+ the four lifecycle ops were reachable only by importing `MemoryStore` and calling
11
+ them from Python. A design that hands lifecycle to an operator and gives the
12
+ operator no lever is not a position, it is an omission.
13
+
14
+ Why a separate entry point rather than a flag on `bantamkit-mcp`: that process
15
+ speaks the MCP protocol over stdout, so anything a lifecycle report printed there
16
+ would corrupt the wire. Lifecycle needs a stream it owns. `python -m` is also the
17
+ invocation this repo already treats as the one a host reaches for when the
18
+ console script is not on PATH (see the `__main__` guard in `mcpserver.py`), and
19
+ it adds no packaging surface and no dependency.
20
+
21
+ Scope is deliberately the writable project layer only. `Memory.layered()` also
22
+ mounts read-only grants and the profile store, and `MemoryStore.compact()`
23
+ touches neither; resolving through `discover_project_store` keeps the CLI unable
24
+ to archive a store it does not own, rather than merely unlikely to.
25
+
26
+ Exit codes: 0 success, 1 an operational failure the operator must act on (over
27
+ budget, malformed fact, refused restore), 2 argparse usage error.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import sys
34
+ from pathlib import Path
35
+
36
+ from bantamkit.client import BantamError
37
+ from bantamkit.memory.layers import discover_project_store
38
+ from bantamkit.memory.store import (
39
+ DEFAULT_INDEX_BUDGET,
40
+ MemoryBudgetExceeded,
41
+ MemoryStore,
42
+ MemoryValidationError,
43
+ )
44
+
45
+ #: The one place this CLI's own name is spelled, and `runtime-ts` has the same constant
46
+ #: under the same name (`PROG` in `src/memory/cli.ts`) holding `bantamkit-memory`. The
47
+ #: `memorycli` conformance suite compares the two CLIs after substituting one for the
48
+ #: other, so everything downstream of it -- the usage line, every `...: error:` prefix,
49
+ #: the sentence `main` prints for a store it could not read, and the two remediation
50
+ #: lines that name a command for the operator to RUN -- has to move with it or the two
51
+ #: halves drift apart one string at a time.
52
+ #:
53
+ #: One remediation line in this runtime is NOT downstream of this constant and cannot be
54
+ #: without the MCP server importing this module: the `index-budget-low` sentence in
55
+ #: `mcpserver.py` spells the same command as a literal. It is held to this value from the
56
+ #: outside instead, by `tests/test_status_surface.py`'s
57
+ #: `test_the_index_remedy_names_the_command_this_install_actually_provides`.
58
+ _PROG = "python -m bantamkit.memory"
59
+
60
+
61
+ def _lf_utf8(stream: object) -> None:
62
+ """Make one of this process's text streams write LF and UTF-8 on every platform.
63
+
64
+ `sys.stdout` and `sys.stderr` are text streams opened with `newline=None`, which
65
+ translates every `\n` to `os.linesep` on the way out: a no-op on macOS and Linux, and
66
+ CRLF on Windows. Their encoding is the locale's, which on Windows is a code page, not
67
+ UTF-8. So the same command run on the same store printed different BYTES on Windows than
68
+ it did here, and the port -- whose `process.stdout.write` emits LF and UTF-8 everywhere
69
+ -- was byte-identical to this CLI on one operating system and not on the other.
70
+
71
+ `mcpserver.py`'s `_print_assets_root`, `_print_mcp_report` and `_print_status_line` each
72
+ solve their own half of this by writing through `sys.stdout.buffer`, and that is the
73
+ established idiom in this repository. It CANNOT be the idiom here, and the reason is
74
+ measured rather than assumed: on Windows' defaults, reproduced on macOS with
75
+ `TextIOWrapper(..., encoding="cp1252", newline="\r\n")`, `-h` emits 16 CRLFs and
76
+ `status --nope` emits 3 -- and every one of them is written by `argparse`, into
77
+ `sys.stdout`/`sys.stderr` by name, from a frame no call site in this module owns. A sweep
78
+ of the `print()` calls below would have fixed the 6 CRLFs of `status` and left the 16 of
79
+ `-h`. The stream is the thing that translates, so the stream is the thing to fix.
80
+
81
+ Guarded on `reconfigure` rather than on a type, because `sys.stdout` is not always a
82
+ `TextIOWrapper`: `pytest`'s capture, a `StringIO` and a closed-stdout `pythonw` all reach
83
+ this line, and a stream that cannot be reconfigured is one that was never doing platform
84
+ translation in the first place.
85
+ """
86
+ reconfigure = getattr(stream, "reconfigure", None)
87
+ if reconfigure is not None:
88
+ reconfigure(encoding="utf-8", newline="\n")
89
+
90
+
91
+ def _positive(text: str) -> int:
92
+ """A budget of 0 or less silently makes every save fail; refuse it at the edge."""
93
+ value = int(text)
94
+ if value < 1:
95
+ raise argparse.ArgumentTypeError("must be >= 1")
96
+ return value
97
+
98
+
99
+ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
100
+ parser = argparse.ArgumentParser(
101
+ prog=_PROG,
102
+ description=(
103
+ "Operator lifecycle for a bantamkit memory store: inspect, lint, "
104
+ "compact, archive and restore. Not an agent surface."
105
+ ),
106
+ )
107
+ subs = parser.add_subparsers(dest="command", required=True)
108
+
109
+ def common(sub: argparse.ArgumentParser) -> argparse.ArgumentParser:
110
+ stores = sub.add_mutually_exclusive_group()
111
+ stores.add_argument("--store", help="memory store path (skips project discovery)")
112
+ stores.add_argument(
113
+ "--start", help="directory to start project-store discovery from (default: cwd)"
114
+ )
115
+ sub.add_argument(
116
+ "--budget",
117
+ type=_positive,
118
+ default=DEFAULT_INDEX_BUDGET,
119
+ metavar="BYTES",
120
+ help=f"index byte budget (default: {DEFAULT_INDEX_BUDGET})",
121
+ )
122
+ return sub
123
+
124
+ common(subs.add_parser("status", help="index size, budget, headroom, archive count"))
125
+ common(subs.add_parser("lint", help="exit 1 if the store is malformed or over budget"))
126
+ compact = common(subs.add_parser("compact", help="archive the stalest facts"))
127
+ compact.add_argument(
128
+ "--reserve",
129
+ type=_positive,
130
+ default=None,
131
+ metavar="BYTES",
132
+ help="headroom to leave below the budget (default: the largest index line kept)",
133
+ )
134
+ common(subs.add_parser("archived", help="list what compaction has moved out"))
135
+ archive = common(subs.add_parser("archive", help="move one named fact out"))
136
+ archive.add_argument("name", help="name of the fact to archive")
137
+ restore = common(subs.add_parser("restore", help="move an archived fact back"))
138
+ restore.add_argument("name", help="name of the archived fact")
139
+
140
+ return parser.parse_args(argv)
141
+
142
+
143
+ def _open(args: argparse.Namespace) -> MemoryStore:
144
+ if args.store is not None:
145
+ if not args.store:
146
+ raise SystemExit("--store requires a non-empty path")
147
+ root = Path(args.store)
148
+ else:
149
+ root = discover_project_store(args.start)
150
+ return MemoryStore(root, index_budget=args.budget)
151
+
152
+
153
+ def _size(store: MemoryStore) -> int:
154
+ return len(store.index_text().encode())
155
+
156
+
157
+ def _cmd_status(store: MemoryStore, args: argparse.Namespace) -> int:
158
+ size = _size(store)
159
+ print(f"store: {store.root}")
160
+ print(f"facts: {len(store._facts())}")
161
+ print(f"index: {size} bytes")
162
+ print(f"budget: {store.index_budget}")
163
+ print(f"headroom: {store.index_budget - size}")
164
+ print(f"archived: {len(store.archived())}")
165
+ return 0
166
+
167
+
168
+ def _cmd_lint(store: MemoryStore, args: argparse.Namespace) -> int:
169
+ try:
170
+ store.lint()
171
+ except MemoryBudgetExceeded:
172
+ # Not the store's own message: that one names `compact()`, which is a Python
173
+ # call and the wrong remedy to hand someone holding a shell.
174
+ where = f"--store {store.root}"
175
+ print(
176
+ f"lint: FAIL — index is {_size(store)} bytes, budget is {store.index_budget}\n"
177
+ f" try: {_PROG} compact {where} "
178
+ f"--budget {store.index_budget}",
179
+ file=sys.stderr,
180
+ )
181
+ return 1
182
+ except MemoryValidationError as e:
183
+ print(f"lint: FAIL — {e}", file=sys.stderr)
184
+ return 1
185
+ print(f"lint: ok — {len(store._facts())} facts, {_size(store)}/{store.index_budget} bytes")
186
+ return 0
187
+
188
+
189
+ def _cmd_compact(store: MemoryStore, args: argparse.Namespace) -> int:
190
+ before = _size(store)
191
+ result = store.compact(reserve=args.reserve)
192
+ print(f"compacted {len(result.archived)} fact(s)")
193
+ print(
194
+ f"index: {before} -> {result.index_after} bytes "
195
+ f"(budget {result.budget}, target {result.target}, "
196
+ f"reserve {result.reserve}, headroom {result.headroom})"
197
+ )
198
+ if not result.archived:
199
+ print("nothing to archive — the index is already at or below the target")
200
+ return 0
201
+ # `archive/` is on disk and nothing reads it back on its own, so the names have to
202
+ # land here or the operator never learns what left.
203
+ print(f"archived -> {result.archive_dir}")
204
+ for fact in result.archived:
205
+ print(f" {fact.name} ({fact.type}, {fact.index_bytes} bytes)")
206
+ print(f"restore one with: {_PROG} restore <name> --store {store.root}")
207
+ return 0
208
+
209
+
210
+ def _cmd_archived(store: MemoryStore, args: argparse.Namespace) -> int:
211
+ names = store.archived()
212
+ print(f"archived facts: {len(names)} ({store.root / 'archive'})")
213
+ for name in names:
214
+ print(f" {name}")
215
+ return 0
216
+
217
+
218
+ def _cmd_archive(store: MemoryStore, args: argparse.Namespace) -> int:
219
+ try:
220
+ store.archive(args.name)
221
+ except MemoryValidationError as e:
222
+ print(f"archive failed: {e}", file=sys.stderr)
223
+ return 1
224
+ print(f"archived '{args.name}' — index now {_size(store)}/{store.index_budget} bytes")
225
+ return 0
226
+
227
+
228
+ def _cmd_restore(store: MemoryStore, args: argparse.Namespace) -> int:
229
+ try:
230
+ store.restore(args.name)
231
+ except MemoryBudgetExceeded:
232
+ print(
233
+ f"restore failed: '{args.name}' would put the index over the budget of "
234
+ f"{store.index_budget} bytes. Nothing changed — raise --budget or compact first.",
235
+ file=sys.stderr,
236
+ )
237
+ return 1
238
+ except MemoryValidationError as e:
239
+ print(f"restore failed: {e}", file=sys.stderr)
240
+ return 1
241
+ print(f"restored '{args.name}' — index now {_size(store)}/{store.index_budget} bytes")
242
+ return 0
243
+
244
+
245
+ _COMMANDS = {
246
+ "status": _cmd_status,
247
+ "lint": _cmd_lint,
248
+ "compact": _cmd_compact,
249
+ "archived": _cmd_archived,
250
+ "archive": _cmd_archive,
251
+ "restore": _cmd_restore,
252
+ }
253
+
254
+
255
+ def main(argv: list[str] | None = None) -> int:
256
+ """Run one subcommand and return its exit code. The exit code is the ONLY channel.
257
+
258
+ `status`, `compact` and `archived` catch nothing of their own -- there is no
259
+ remediation to offer for a store that cannot be read, only a report -- so before
260
+ this clause a `MemoryValidationError` out of `_listing` unwound all the way through
261
+ CPython, which printed a two-stage traceback carrying interpreter absolute paths and
262
+ line numbers from inside `store.py`. Exit 1 either way; the difference is entirely in
263
+ what the operator is handed, and a stack trace names this repository's files rather
264
+ than the store the operator asked about.
265
+
266
+ `BantamError` and nothing wider, exactly as `runtime-ts/src/memory/cli.ts` has it: a
267
+ bug in bantamkit is still a traceback, because that one IS a report for a maintainer.
268
+ What is caught here is the class of failures that are ABOUT the operator's store --
269
+ unreadable, unlistable, malformed -- and every one of them already carries a sentence
270
+ that names the directory and says what the consequence would have been. The prefix is
271
+ this CLI's own `prog`, so the line reads as the program speaking rather than as an
272
+ error string from nowhere, and it is byte-identical to the port's after the one
273
+ substitution the conformance suite makes.
274
+
275
+ `SystemExit` is deliberately not caught: argparse's usage errors are exit 2 and
276
+ `_open`'s empty-`--store` refusal is its own sentence already on stderr.
277
+ """
278
+ # BEFORE `_parse_args`, because `-h` and every usage error are written by argparse
279
+ # straight into these two streams and never come back through this frame.
280
+ _lf_utf8(sys.stdout)
281
+ _lf_utf8(sys.stderr)
282
+ args = _parse_args(argv)
283
+ try:
284
+ return _COMMANDS[args.command](_open(args), args)
285
+ except BantamError as e:
286
+ print(f"{_PROG}: {e}", file=sys.stderr)
287
+ return 1
288
+
289
+
290
+ if __name__ == "__main__":
291
+ raise SystemExit(main())