bantamkit-mcp 0.26.0
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.
- package/LICENSE +21 -0
- package/README.md +398 -0
- package/assets/contracts/default.yaml +90 -0
- package/assets/evals/devteam/manifest.yaml +351 -0
- package/assets/evals/devteam/repo/HISTORY.md +18 -0
- package/assets/evals/devteam/repo/README.md +12 -0
- package/assets/evals/devteam/repo/docs/architecture.md +17 -0
- package/assets/evals/devteam/repo/docs/runbook.md +10 -0
- package/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
- package/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
- package/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
- package/assets/evals/devteam/repo/src/ledger/config.py +35 -0
- package/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
- package/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
- package/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
- package/assets/evals/devteam/repo/src/ledger/report.py +9 -0
- package/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
- package/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
- package/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
- package/assets/evals/devteam/repo/tests/test_posting.py +13 -0
- package/assets/evals/devteam/repo/tests/test_settle.py +9 -0
- package/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
- package/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
- package/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
- package/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
- package/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
- package/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-137.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-261.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-388.yaml +37 -0
- package/assets/evals/fixtures/.gitkeep +0 -0
- package/assets/evals/fixtures/catalog.json +6 -0
- package/assets/evals/perturbations/task-completion.yaml +576 -0
- package/assets/evals/tasks/.gitkeep +0 -0
- package/assets/evals/tasks/extract-contact.yaml +14 -0
- package/assets/evals/tasks/extract-invoice.yaml +14 -0
- package/assets/evals/tasks/extract-order.yaml +15 -0
- package/assets/evals/tasks/extract-schedule.yaml +14 -0
- package/assets/evals/tasks/extract-versions.yaml +17 -0
- package/assets/evals/tasks/nav-prod-port.yaml +84 -0
- package/assets/evals/tasks/nav-release-bundle.yaml +87 -0
- package/assets/evals/tasks/recall-audit-retention.yaml +17 -0
- package/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
- package/assets/evals/tasks/recall-db-port.yaml +17 -0
- package/assets/evals/tasks/recall-deploy.yaml +13 -0
- package/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
- package/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
- package/assets/evals/tasks/recall-oncall.yaml +13 -0
- package/assets/evals/tasks/recall-org-quota.yaml +18 -0
- package/assets/evals/tasks/recall-owner.yaml +13 -0
- package/assets/evals/tasks/shop-basket-total.yaml +10 -0
- package/assets/evals/tasks/shop-cheapest.yaml +9 -0
- package/assets/evals/tasks/shop-compare.yaml +9 -0
- package/assets/evals/tasks/shop-gadget-value.yaml +9 -0
- package/assets/evals/tasks/shop-stock-total.yaml +9 -0
- package/assets/evals/tasks/shop-total.yaml +9 -0
- package/assets/profiles/default.yaml +31 -0
- package/assets/profiles/patient.yaml +31 -0
- package/assets/rubrics/.gitkeep +0 -0
- package/assets/rubrics/code-quality.yaml +20 -0
- package/assets/rubrics/grounded-completion.yaml +37 -0
- package/assets/rubrics/task-completion.yaml +28 -0
- package/assets/schemas/shiftwork-checkpoint.json +188 -0
- package/assets/skills/.gitkeep +0 -0
- package/assets/skills/file-graph.md +7 -0
- package/assets/skills/memory.md +35 -0
- package/assets/tools/.gitkeep +0 -0
- package/assets/tools/bantamkit_read.json +48 -0
- package/assets/tools/bantamkit_status.json +25 -0
- package/assets/tools/build_identity.json +17 -0
- package/assets/tools/document_list.json +12 -0
- package/assets/tools/document_read.json +31 -0
- package/assets/tools/file_graph.json +12 -0
- package/assets/tools/memory_compact.json +31 -0
- package/assets/tools/memory_recall.json +38 -0
- package/assets/tools/memory_save.json +61 -0
- package/assets/tools/shiftwork_clock_in.json +25 -0
- package/assets/tools/shiftwork_clock_out.json +60 -0
- package/assets/tools/shiftwork_status.json +25 -0
- package/assets/tools/skill_audit.json +70 -0
- package/assets/tools/validate_json.json +31 -0
- package/dist/assets.d.ts +48 -0
- package/dist/assets.js +109 -0
- package/dist/charsets.d.ts +17 -0
- package/dist/charsets.js +580 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +280 -0
- package/dist/contract.d.ts +145 -0
- package/dist/contract.js +608 -0
- package/dist/docread.d.ts +357 -0
- package/dist/docread.js +3315 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +14 -0
- package/dist/eventlog.d.ts +137 -0
- package/dist/eventlog.js +294 -0
- package/dist/htmlentities.d.ts +2 -0
- package/dist/htmlentities.js +16 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +13 -0
- package/dist/mcp/identity.d.ts +25 -0
- package/dist/mcp/identity.js +275 -0
- package/dist/mcp/pyargs.d.ts +80 -0
- package/dist/mcp/pyargs.js +362 -0
- package/dist/mcp/sdkjson.d.ts +56 -0
- package/dist/mcp/sdkjson.js +91 -0
- package/dist/mcp/server.d.ts +45 -0
- package/dist/mcp/server.js +740 -0
- package/dist/mcp/status.d.ts +210 -0
- package/dist/mcp/status.js +357 -0
- package/dist/mcp/transport.d.ts +68 -0
- package/dist/mcp/transport.js +111 -0
- package/dist/mcpreport.d.ts +214 -0
- package/dist/mcpreport.js +758 -0
- package/dist/memory/cli.d.ts +2 -0
- package/dist/memory/cli.js +396 -0
- package/dist/memory/component.d.ts +256 -0
- package/dist/memory/component.js +497 -0
- package/dist/memory/factfile.d.ts +120 -0
- package/dist/memory/factfile.js +406 -0
- package/dist/memory/layers.d.ts +92 -0
- package/dist/memory/layers.js +410 -0
- package/dist/memory/pyfs.d.ts +521 -0
- package/dist/memory/pyfs.js +1608 -0
- package/dist/memory/pyyaml.d.ts +155 -0
- package/dist/memory/pyyaml.js +868 -0
- package/dist/memory/store.d.ts +478 -0
- package/dist/memory/store.js +1034 -0
- package/dist/pyargparse.d.ts +141 -0
- package/dist/pyargparse.js +813 -0
- package/dist/pyjson.d.ts +110 -0
- package/dist/pyjson.js +524 -0
- package/dist/pyjsonschema.d.ts +50 -0
- package/dist/pyjsonschema.js +951 -0
- package/dist/shiftwork.d.ts +60 -0
- package/dist/shiftwork.js +324 -0
- package/dist/skillaudit.d.ts +172 -0
- package/dist/skillaudit.js +914 -0
- package/dist/statusline.d.ts +106 -0
- package/dist/statusline.js +281 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ink01101011
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# bantamkit-mcp
|
|
2
|
+
|
|
3
|
+
The bantamkit MCP server as a pure-Node package: `npx bantamkit-mcp`, no Python, no
|
|
4
|
+
`uv`, no `pipx`, no interpreter bootstrap. It serves the same eleven tools, the same
|
|
5
|
+
`bantamkit_status` prompt and the same two resource templates as `runtime-py`'s server,
|
|
6
|
+
reads and writes the same memory store, and is checked against the Python server frame by
|
|
7
|
+
frame — 4500+ conformance cases, with every intentional difference written down as a
|
|
8
|
+
ruling. It installs **two** commands, not one — the server and `bantamkit-memory`, the
|
|
9
|
+
operator CLI for memory-store lifecycle; see *The second bin* below.
|
|
10
|
+
|
|
11
|
+
Every number on this page was measured on the machine that wrote it, with a command you
|
|
12
|
+
can rerun. Where something was not measured, it says so.
|
|
13
|
+
|
|
14
|
+
## Install and run
|
|
15
|
+
|
|
16
|
+
```jsonc
|
|
17
|
+
// .mcp.json — project scope
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"bantamkit": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "bantamkit-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That is the whole install. No `pip`, no venv, no `PYTHONPATH`. `runtime-ts/mcp.json.example`
|
|
29
|
+
in the repository is the annotated version, with the four forms and the two measured
|
|
30
|
+
questions that decide between them.
|
|
31
|
+
|
|
32
|
+
**Pass `-y`.** `npx` historically prompted before installing a package it had not seen,
|
|
33
|
+
and stdin here is the JSON-RPC channel: a prompt that consumed one frame looks like a
|
|
34
|
+
server that lost a request. npm 11.6.2 was measured sending that prompt to stderr rather
|
|
35
|
+
than reading stdin, but that is version-dependent and `-y` costs nothing.
|
|
36
|
+
|
|
37
|
+
**Pin the version.** `npx -y bantamkit-mcp` resolves `latest` and caches it, so two
|
|
38
|
+
machines can run different builds from one identical config line. See *Silent version
|
|
39
|
+
float* below — it is the failure this package makes easiest to hit and hardest to see.
|
|
40
|
+
|
|
41
|
+
### Measured: what a cold start costs
|
|
42
|
+
|
|
43
|
+
`node tools/conformance/npx-cold-start.mjs` in the repo packs the tarball, installs it
|
|
44
|
+
from disk into a cache that has never seen it, and drives a real MCP handshake. On
|
|
45
|
+
node v25.2.1 / npm 11.6.2, macOS (darwin 25.5.0), Apple silicon:
|
|
46
|
+
|
|
47
|
+
| | cold cache | warm cache |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| wall to the first JSON-RPC frame | **3.90 s** and **5.09 s**, two runs | **1.14 s** and **1.11 s** |
|
|
50
|
+
| npm/npx bytes on stderr | 211 (an `npm notice` about npm itself) | 0 |
|
|
51
|
+
|
|
52
|
+
Both cold figures are reported rather than averaged, and the two in that column are two
|
|
53
|
+
runs from **one sitting on 2026-08-25** — the column is labelled "two runs" and one
|
|
54
|
+
execution cannot honestly fill it.
|
|
55
|
+
|
|
56
|
+
**The spread is bigger than a range would suggest, and it is the network.** Five cold
|
|
57
|
+
runs on that same day, same machine, same commit, measured **3.68 / 3.90 / 4.66 / 5.09 /
|
|
58
|
+
9.66 s**. An earlier revision of this table read "4.13 s and 7.36 s" and called a cold
|
|
59
|
+
start "worth about 4-7 s here"; three of those five runs fall outside that range, so the
|
|
60
|
+
range is dropped rather than re-fitted. What a reader should take from the cold column is
|
|
61
|
+
an ORDER OF MAGNITUDE — seconds, dominated by the registry round trip, worse on a slower
|
|
62
|
+
link — and not a number to compare a future run against. The warm figure is the one that
|
|
63
|
+
is a property of this package, and it is stable across every run above.
|
|
64
|
+
|
|
65
|
+
- **92 packages** installed (top-level under `node_modules`, scope-aware); **111**
|
|
66
|
+
`package.json` **under `node_modules`**, which is the count of packages actually
|
|
67
|
+
installed:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
find "$BED/cold/cache/_npx/<hash>/node_modules" -type f -name package.json | wc -l # 111
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Counting from the `_npx/<hash>` directory instead gives **112**, and the extra file is
|
|
74
|
+
not a package: npx synthesises a `package.json` at that root holding the `file:` spec
|
|
75
|
+
and an `_npx.packages` array. The method is written down here because the 112 was read
|
|
76
|
+
once as a drift of one and it is not one — it is a different denominator. Reproduce
|
|
77
|
+
either with `node tools/conformance/npx-cold-start.mjs --keep`, which prints the scratch
|
|
78
|
+
path it leaves behind.
|
|
79
|
+
- **15.7 MB** of files under `$npm_config_cache/_npx/<hash>` (26 MB of allocated blocks by
|
|
80
|
+
`du`), **37.8 MB** for the whole cache including npm's content-addressable store (this
|
|
81
|
+
read 37.7 until 2026-08-25; all four cold runs that day printed 37.8).
|
|
82
|
+
- `bantamkit-mcp` itself is **0.9 MB** of that (1.2 MB allocated). The rest is
|
|
83
|
+
`@modelcontextprotocol/sdk@1.30.0`'s dependency tree, which pulls in `express`, `cors`,
|
|
84
|
+
`body-parser`, `ajv`, `eventsource`, `hono` and `express-rate-limit` — the SDK's HTTP
|
|
85
|
+
transport, none of which this stdio server uses. That is the SDK's shape, not a choice
|
|
86
|
+
this package makes; it declares exactly one runtime dependency.
|
|
87
|
+
- The tarball is **~266 KB**, 137 files: 50 in `dist/`, 84 in `assets/`, plus `LICENSE`,
|
|
88
|
+
`package.json` and this README. (The exact byte count moves whenever this file does;
|
|
89
|
+
the gate prints it, and `test/packaging.test.mjs` pins the file list.)
|
|
90
|
+
|
|
91
|
+
### Measured: what happens when the registry is not reachable
|
|
92
|
+
|
|
93
|
+
A cold `npx` start needs the network. Measured with the registry pointed at a closed port
|
|
94
|
+
and at a blackholed address, driving the same handshake:
|
|
95
|
+
|
|
96
|
+
| registry | seconds before `npx` gave up | bytes the client saw on stdout |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| connection refused (`http://127.0.0.1:1/`) | **140.3 s** | **0** |
|
|
99
|
+
| packets dropped (`http://192.0.2.1:443/`) | **590.4 s** | **0** |
|
|
100
|
+
|
|
101
|
+
**Read that as: the failure is silence.** Nothing appears on the JSON-RPC channel for the
|
|
102
|
+
whole interval, and then the process exits 1. An MCP host does not see "no network"; it
|
|
103
|
+
sees a server that accepted the launch and never answered `initialize`, and it reports its
|
|
104
|
+
own handshake timeout. The npm error text goes to stderr, which most hosts do not surface.
|
|
105
|
+
|
|
106
|
+
A warm cache does not have this problem — `npx` runs the cached install without contacting
|
|
107
|
+
the registry — so this bites a new machine, a cleared cache, or a CI runner, which is
|
|
108
|
+
exactly when nobody is watching.
|
|
109
|
+
|
|
110
|
+
If offline operation matters, do not use `npx`. Run `npm i -g bantamkit-mcp` once and
|
|
111
|
+
point the config at the installed binary, or keep using `tools/bantamkit-mcp`.
|
|
112
|
+
|
|
113
|
+
### Measured: `npx` is not on a login-less PATH
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
$ env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin sh -c 'command -v npx'
|
|
117
|
+
$ echo $?
|
|
118
|
+
1
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On the machine this was written on, `node`, `npm` and `npx` exist only on the
|
|
122
|
+
mise-injected PATH, and mise is activated from `~/.zshrc` — an **interactive** shell rc.
|
|
123
|
+
`launchctl getenv PATH` is unset, so a GUI-launched application inherits launchd's default
|
|
124
|
+
`/usr/bin:/bin:/usr/sbin:/sbin`, where none of the three is found.
|
|
125
|
+
|
|
126
|
+
**What a GUI-launched MCP host actually sees is `ENOENT` on `npx`.** Not a bantamkit
|
|
127
|
+
error, not a bad config — the host reports that it could not spawn the command, which is
|
|
128
|
+
the least informative message in the whole chain. This is the RB-P96 failure class
|
|
129
|
+
relocated: the sh launcher was guaranteed present because it was a file in the repository;
|
|
130
|
+
`npx` is guaranteed present only if the host's *process* environment has it.
|
|
131
|
+
|
|
132
|
+
Two ways out, both in `runtime-ts/mcp.json.example`:
|
|
133
|
+
|
|
134
|
+
```jsonc
|
|
135
|
+
// absolute path to the npx you actually have
|
|
136
|
+
{ "command": "/Users/you/.local/share/mise/installs/node/latest/bin/npx",
|
|
137
|
+
"args": ["-y", "bantamkit-mcp@0.25.0"] }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```jsonc
|
|
141
|
+
// or install once and skip npx entirely: npm i -g bantamkit-mcp
|
|
142
|
+
{ "command": "/usr/local/bin/bantamkit-mcp", "args": [] }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## What `npx` gives up versus `tools/bantamkit-mcp`
|
|
146
|
+
|
|
147
|
+
The sh launcher is not obsolete. It guarantees things `npx` cannot, and this table is the
|
|
148
|
+
prep probe's, unsoftened.
|
|
149
|
+
|
|
150
|
+
| guarantee | `tools/bantamkit-mcp` | `npx bantamkit-mcp` |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| present in every checkout **and** every worktree | yes — it is a file in the tree | **gone.** Depends on the host's PATH, not on the project directory |
|
|
153
|
+
| runs *this* checkout's code | yes | **gone for developers too.** Testing a worktree needs an absolute `node <worktree>/dist/cli.js`, or the host silently runs the published build |
|
|
154
|
+
| code from the worktree, deps from the main checkout | yes (`PYTHONPATH` + `PYTHONSAFEPATH`) | **no Node analogue.** `NODE_PATH` is ignored by ESM and there is no `-P`. `npm link` and workspaces are a different failure surface, not the same one solved |
|
|
155
|
+
| stdin is the JSON-RPC channel | yes | yes, with `-y` |
|
|
156
|
+
| names the cause when dependencies are missing | yes | the analogous failure is *no network*, and it has no message at all — see the table above |
|
|
157
|
+
| starts without a network | yes | **no**, on a cold cache |
|
|
158
|
+
| `--which`, for diagnosing which endpoint answered | the flag exists | **not in the npx CLI** — but `tools/bantamkit-mcp-node --which` has it, see below |
|
|
159
|
+
| one config line, no clone, no venv | no | **yes.** This is the whole reason the package exists |
|
|
160
|
+
|
|
161
|
+
### `--which` is a launcher flag, not a package flag
|
|
162
|
+
|
|
163
|
+
This section previously read "`--which` is deleted, not ported" and said "there is no
|
|
164
|
+
Node `--which`, and none is planned". Both are now false, and the second was made false
|
|
165
|
+
inside this repository: `tools/bantamkit-mcp-node` ships `--which`, and
|
|
166
|
+
`runtime-ts/test/launcher.test.mjs` runs it — including on a checkout that has never been
|
|
167
|
+
built, which is the case the Python flag's `find_spec` was chosen for. It prints
|
|
168
|
+
`checkout=`, `deps_root=`, `runtime=node`, `node=`, `entry=` (suffixed `(missing)` when
|
|
169
|
+
`dist/` is absent) and `sdk=`.
|
|
170
|
+
|
|
171
|
+
What is genuinely not ported is `--which` **on the published package**, and the reason is
|
|
172
|
+
the line above it in the table: `npx bantamkit-mcp` does not run a checkout, so
|
|
173
|
+
`checkout=` and `source=` have nothing to report. The question a reader has about an npx
|
|
174
|
+
endpoint is not *where did this resolve* but *which build answered*, and that is
|
|
175
|
+
`build_identity` — a tool on the wire rather than a flag on a launcher, and what the next
|
|
176
|
+
section is about.
|
|
177
|
+
|
|
178
|
+
The old wording came from a real defect, which is now closed. `tools/bantamkit-mcp:47`
|
|
179
|
+
used to name `tools/mcpreach/mcpreach.py` as `--which`'s consumer and `docs/mcp.md` used
|
|
180
|
+
to document a five-value exit-code interface for that program (`0` REACHABLE, `1`
|
|
181
|
+
UNREACHABLE, `2` FOREIGN, `3` UNDECLARED, `4` NO_ENV). **That program has never existed** —
|
|
182
|
+
`git log --all --diff-filter=A -- '*mcpreach*'` is empty across every ref in this
|
|
183
|
+
repository, and `docs/eval.md` records the decision not to ship it, the half-built checker
|
|
184
|
+
having "never been seen to fire". Both citations were rewritten on 2026-08-24 to name what
|
|
185
|
+
actually runs, and `runtime-py/tests/test_doc_commands_gate.py` is now red if any fenced
|
|
186
|
+
shell block in the repository names a `tools/` program that is not in the tree.
|
|
187
|
+
|
|
188
|
+
## Silent version float, and the instrument for it
|
|
189
|
+
|
|
190
|
+
This is the one that will bite a team hardest.
|
|
191
|
+
|
|
192
|
+
`npx -y bantamkit-mcp` resolves the dist-tag `latest` and caches the result. Two people
|
|
193
|
+
with byte-identical `.mcp.json` files can be running different builds — one resolved last
|
|
194
|
+
week, one resolved this morning — and nothing in the config, the logs or the tool output
|
|
195
|
+
says so. The sh launcher could not do this: it ran the checkout you were standing in.
|
|
196
|
+
|
|
197
|
+
Call `build_identity`. Three fields answer it:
|
|
198
|
+
|
|
199
|
+
- **`runtime`** — `"node"` here, absent on the Python server. Its *presence* is the
|
|
200
|
+
discriminator, and it is folded into `build_id` so the two lineages cannot collide even
|
|
201
|
+
if their code digests agreed.
|
|
202
|
+
- **`assets_digest`** — sha256 over every byte of the asset pack, computed identically in
|
|
203
|
+
both runtimes and **verified equal**: `sha256:b03141bf…` over 83 files from Python and
|
|
204
|
+
from Node. This is the cross-runtime instrument; two machines disagreeing here are
|
|
205
|
+
serving different data.
|
|
206
|
+
- **`code_digest`** / **`build_id`** — over `dist/**/*.js` on this side and `*.py` on the
|
|
207
|
+
other, so they are *required* to differ across runtimes and required to match across two
|
|
208
|
+
installs of the same version. `build_id` differing between two teammates on the same
|
|
209
|
+
version string is the float, caught.
|
|
210
|
+
|
|
211
|
+
The output carries a `cross_runtime` sentence saying in plain words which fields are
|
|
212
|
+
comparable. `git_commit` is refused rather than guessed: an installed npm tarball carries
|
|
213
|
+
no repository, and reading a checkout's HEAD would describe the tree rather than the bytes
|
|
214
|
+
that were imported (RB-P84).
|
|
215
|
+
|
|
216
|
+
**Pin the version in the config** if you want this to be a non-issue:
|
|
217
|
+
`"args": ["-y", "bantamkit-mcp@0.25.0"]`.
|
|
218
|
+
|
|
219
|
+
## The second bin: `bantamkit-memory`
|
|
220
|
+
|
|
221
|
+
`package.json` declares **two** bins, so an install puts two commands on the path. Packed
|
|
222
|
+
and installed into an empty scratch directory, that is what arrives:
|
|
223
|
+
|
|
224
|
+
```console
|
|
225
|
+
$ npm install ./bantamkit-mcp-0.25.0.tgz
|
|
226
|
+
added 95 packages in 5s
|
|
227
|
+
$ ls -l node_modules/.bin/
|
|
228
|
+
bantamkit-mcp -> ../bantamkit-mcp/dist/cli.js
|
|
229
|
+
bantamkit-memory -> ../bantamkit-mcp/dist/memory/cli.js
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`bantamkit-mcp` is the server the `.mcp.json` line at the top launches, and its stdout is
|
|
233
|
+
the JSON-RPC wire — not a thing you run by hand. `bantamkit-memory` is the operator CLI
|
|
234
|
+
for memory-store lifecycle, and it prints reports. That is the whole reason it is a
|
|
235
|
+
second bin instead of a subcommand: lifecycle output on the server's stdout would corrupt
|
|
236
|
+
the transport, and `bantamkit-mcp`'s help is a byte-compared artifact against
|
|
237
|
+
`python -m bantamkit.mcpserver -h`, which a subparsers action would move.
|
|
238
|
+
|
|
239
|
+
Five subcommands, scoped to the writable **project** layer only — read-only grants and the
|
|
240
|
+
profile store are out of its reach by the code path, not by convention:
|
|
241
|
+
|
|
242
|
+
| subcommand | what it does |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `status` | index size, budget, headroom, archive count |
|
|
245
|
+
| `lint` | exit 1 if the store is malformed or over budget |
|
|
246
|
+
| `compact` | archive the stalest facts until the index fits |
|
|
247
|
+
| `archived` | list what compaction has moved out |
|
|
248
|
+
| `restore NAME` | move an archived fact back |
|
|
249
|
+
|
|
250
|
+
Each takes `--store PATH` or `--start DIR`; with neither, it resolves the project store
|
|
251
|
+
the way `Memory.layered()` does. Exit codes are `0` success, `1` a failure the operator
|
|
252
|
+
must act on (over budget, a malformed fact, a refused restore), `2` a usage error — so
|
|
253
|
+
`lint` drops into a pre-commit hook or a CI job unchanged.
|
|
254
|
+
|
|
255
|
+
Driven off `node_modules/.bin/` from the install above, against a scratch five-fact store:
|
|
256
|
+
|
|
257
|
+
```console
|
|
258
|
+
$ bantamkit-memory status --store store
|
|
259
|
+
store: store
|
|
260
|
+
facts: 5
|
|
261
|
+
index: 576 bytes
|
|
262
|
+
budget: 24000
|
|
263
|
+
headroom: 23424
|
|
264
|
+
archived: 0
|
|
265
|
+
$ bantamkit-memory lint --store store --budget 400
|
|
266
|
+
lint: FAIL — index is 576 bytes, budget is 400
|
|
267
|
+
try: bantamkit-memory compact --store store --budget 400
|
|
268
|
+
$ echo $?
|
|
269
|
+
1
|
|
270
|
+
$ bantamkit-memory compact --store store --budget 400
|
|
271
|
+
compacted 3 fact(s)
|
|
272
|
+
index: 576 -> 236 bytes (budget 400, target 267, reserve 133, headroom 164)
|
|
273
|
+
archived -> store/archive
|
|
274
|
+
assets-pack-has-eighty-four-files (project, 133 bytes)
|
|
275
|
+
ci-runner-is-macos-only (project, 97 bytes)
|
|
276
|
+
conformance-runner-entrypoint (reference, 110 bytes)
|
|
277
|
+
restore one with: bantamkit-memory restore <name> --store store
|
|
278
|
+
$ bantamkit-memory archived --store store
|
|
279
|
+
archived facts: 3 (store/archive)
|
|
280
|
+
assets-pack-has-eighty-four-files
|
|
281
|
+
ci-runner-is-macos-only
|
|
282
|
+
conformance-runner-entrypoint
|
|
283
|
+
$ bantamkit-memory restore ci-runner-is-macos-only --store store
|
|
284
|
+
restored 'ci-runner-is-macos-only' — index now 333/24000 bytes
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`compact` printing every name that left is the point: `archive/` is a directory nothing
|
|
288
|
+
reads back on its own, so a compaction whose output is not shown is a silent deletion as
|
|
289
|
+
far as the operator is concerned.
|
|
290
|
+
|
|
291
|
+
Without installing, `npx -p bantamkit-mcp bantamkit-memory status` runs it out of the
|
|
292
|
+
registry. `-p` is not optional — the package name and this bin's name differ, and plain
|
|
293
|
+
`npx bantamkit-memory` would go looking for a package called `bantamkit-memory`. Measured
|
|
294
|
+
here against the local tarball rather than the registry, since this version is not
|
|
295
|
+
published:
|
|
296
|
+
|
|
297
|
+
```console
|
|
298
|
+
$ npx -y -p ./bantamkit-mcp-0.25.0.tgz bantamkit-memory status --store npxstore
|
|
299
|
+
store: npxstore
|
|
300
|
+
facts: 0
|
|
301
|
+
index: 0 bytes
|
|
302
|
+
budget: 24000
|
|
303
|
+
headroom: 24000
|
|
304
|
+
archived: 0
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**The Python install does not provide this command**, which is why the two spellings
|
|
308
|
+
exist. Measured against the distribution in this repository's venv:
|
|
309
|
+
|
|
310
|
+
```console
|
|
311
|
+
$ .venv/bin/python -c "from importlib.metadata import distribution; d=distribution('bantamkit'); print(sorted(e.name for e in d.entry_points if e.group=='console_scripts'))"
|
|
312
|
+
['bantamkit-mcp']
|
|
313
|
+
$ env PATH="$PWD/.venv/bin:/usr/bin:/bin" sh -c 'command -v bantamkit-memory'
|
|
314
|
+
$ echo $?
|
|
315
|
+
1
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
One console script, and it is the server. The Python operator therefore types
|
|
319
|
+
`python -m bantamkit.memory`, and the two CLIs are identical bytes after substituting one
|
|
320
|
+
for the other — except the wrap, because argparse's hanging indent is
|
|
321
|
+
`len(prefix) + len(prog) + 1`. The reason there is no third spelling both installs could
|
|
322
|
+
use lives in one place, the `prog` row of `docs/porting.md`'s divergence table, and this
|
|
323
|
+
page does not restate it. `docs/memory.md`'s *The operator CLI* is the full reference;
|
|
324
|
+
`tools/conformance/suites/memorycli.mjs` is the gate that compares the two.
|
|
325
|
+
|
|
326
|
+
## The default is layered — do not add `--store` by reflex
|
|
327
|
+
|
|
328
|
+
With **no arguments**, which is what the config above passes and what production runs, the
|
|
329
|
+
server binds `Memory.layered`: the project store discovered from the working directory,
|
|
330
|
+
plus the profile layer, plus any `extra_stores` from `.bantamkit/config.yaml`. Recall lines
|
|
331
|
+
are prefixed with their layer — `[project] `, `[extra:<name>]`, `[profile]`.
|
|
332
|
+
|
|
333
|
+
`--store <path>` binds one store and drops the tag. It is a debugging flag. A recall string
|
|
334
|
+
produced under `--store` is not a string the deployment ever emits, and the cold-start gate
|
|
335
|
+
asserts the layered form specifically for that reason.
|
|
336
|
+
|
|
337
|
+
## The asset pack
|
|
338
|
+
|
|
339
|
+
The pack is language-agnostic data (tool manifests, schemas, skills, rubrics, contract
|
|
340
|
+
wording, eval fixtures) that lives at the **repository root**, one level above this
|
|
341
|
+
package, and is shared verbatim with `runtime-py`. npm cannot reach outside a package
|
|
342
|
+
directory, so `scripts/sync-assets.mjs` vendors it into `runtime-ts/assets/` on `prepack`;
|
|
343
|
+
that directory is generated and git-ignored.
|
|
344
|
+
|
|
345
|
+
`build_identity` hashes **every byte of the whole tree**, not just the 20 files the tools
|
|
346
|
+
read, so the pack ships whole — 83 files / 212,480 bytes — or `assets_digest` and
|
|
347
|
+
`build_id` change. `test/packaging.test.mjs` asserts that against what `npm pack` would
|
|
348
|
+
actually put in the tarball, never against the source tree.
|
|
349
|
+
|
|
350
|
+
`assetsRoot()` mirrors `runtime-py/src/bantamkit/assets.py` arm for arm:
|
|
351
|
+
|
|
352
|
+
1. `$BANTAMKIT_ASSETS`, verbatim, with no existence check.
|
|
353
|
+
2. `<package>/assets/` — one level above `dist/`. **This is the arm `npx` uses.**
|
|
354
|
+
3. `<repo>/assets/` — two levels above `dist/`, for a dev checkout with nothing vendored.
|
|
355
|
+
4. Otherwise `AssetNotFound: no assets directory found; set BANTAMKIT_ASSETS`.
|
|
356
|
+
|
|
357
|
+
## Sharing a store with the Python server
|
|
358
|
+
|
|
359
|
+
Both servers read and write the same memory store, and byte-compatibility is the product,
|
|
360
|
+
not a nice-to-have. Two things to know:
|
|
361
|
+
|
|
362
|
+
- **Concurrent writes are safe for the memory store and lossy for the checkpoint, in both
|
|
363
|
+
runtimes.** `index.md` is derived from the `facts/` directory, so the last writer
|
|
364
|
+
re-enumerates everything and the index self-heals — measured over 8 trials of two
|
|
365
|
+
processes × 15 saves, zero disagreements between `facts/` and `index.md`. A checkpoint is
|
|
366
|
+
not derived that way: `shiftwork_clock_out` is read-modify-write and the write depends on
|
|
367
|
+
the document it read, so two concurrent clock-outs on the *same cursor unit* both answer
|
|
368
|
+
`ok` and one history entry is lost. Measured 10/10 trials on Node **and** 10/10 on the
|
|
369
|
+
Python reference — reference behaviour reproduced, not something the port introduced. The
|
|
370
|
+
cursor check serialises the normal case; only the same-unit collision loses.
|
|
371
|
+
- **Do not hand-edit a fact file's `name`, `description` or `type` to a bare non-string.**
|
|
372
|
+
`description: 2026` is a YAML integer, not the text `2026`. Python interpolates it and
|
|
373
|
+
carries on; this port refuses the whole store with `malformed fact file …`. Quote it:
|
|
374
|
+
`description: '2026'`. Every fact the tools *write* is quoted correctly, so this only
|
|
375
|
+
reaches you through an editor. See `src/memory/factfile.ts:257`.
|
|
376
|
+
|
|
377
|
+
## Development
|
|
378
|
+
|
|
379
|
+
```sh
|
|
380
|
+
npm install
|
|
381
|
+
npm run build # tsc -> dist/
|
|
382
|
+
npm test # node --test; `npm test -- packaging` selects one file
|
|
383
|
+
npm pack --dry-run # vendors the pack via prepack, lists the tarball
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
From the repository root:
|
|
387
|
+
|
|
388
|
+
```sh
|
|
389
|
+
node tools/conformance/run.mjs --all # Node vs Python, 4300+ cases
|
|
390
|
+
node tools/conformance/npx-cold-start.mjs # pack, cold npx, warm npx, PATH
|
|
391
|
+
node tools/conformance/npx-cold-start.mjs --offline # + the no-registry probe (~2.5 min)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The conformance harness shells out to a CPython to produce the reference side. That is a
|
|
395
|
+
**development-time** dependency of the test tooling; nothing under `tools/` ships, and
|
|
396
|
+
`files: ["dist", "assets"]` is the whole published surface.
|
|
397
|
+
|
|
398
|
+
Runtime dependencies: exactly one, `@modelcontextprotocol/sdk`, pinned to `1.30.0`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: default
|
|
2
|
+
schema_instruction: "Return ONLY a JSON object matching this JSON Schema. No prose.\n"
|
|
3
|
+
schema_retry: "{error}\nReturn ONLY a JSON object matching the schema."
|
|
4
|
+
critique_feedback: "A reviewer scored your answer {score}/10 (needs >= {threshold}). Feedback: {feedback}\nIf the feedback says a value is missing, unverified, or absent from your evidence, that is a fact you never looked up: call your tools and read the source that has it. Do not reword, generalise, or hedge the previous answer to work around the gap. Then answer again."
|
|
5
|
+
parse_error: "output was not parseable JSON: {detail}"
|
|
6
|
+
validation_error: "JSON does not match schema at '{where}': {detail}"
|
|
7
|
+
json_answer_retry: "Your answer contains no JSON. Restate your final answer as ONLY the JSON requested by the task, with no prose around it."
|
|
8
|
+
loop_note: "(you have now received this exact result {count} times; it will not change. Do something different or give your final answer now)"
|
|
9
|
+
loop_warn: "(STOP calling tools. Give your final answer now, in exactly the format the task asked for.)"
|
|
10
|
+
evidence_line: "{name}({arguments}) -> {observation}"
|
|
11
|
+
evidence_no_observation: "(no observation)"
|
|
12
|
+
evidence_empty: "(no tool calls were made)"
|
|
13
|
+
# The document reader pair's observations (`document_list`, `document_read`). Every string a
|
|
14
|
+
# reader hands back to the model lives here and not in the harness, for the reason the layer
|
|
15
|
+
# rule gives: the mechanics measured in `docread.py` transfer across models, this wording is
|
|
16
|
+
# exactly what does not.
|
|
17
|
+
document_manifest_empty: "no documents are attached to this task"
|
|
18
|
+
document_manifest_part: "{document} ({kind}) part {index} \"{part}\": {rows} rows, numbered 0 to {last}"
|
|
19
|
+
document_manifest_header_row: " row 0 is the header: {row}"
|
|
20
|
+
document_manifest_first_row: " row 1 is the first data row: {row}"
|
|
21
|
+
document_manifest_last_row: " row {index} is the last data row: {row}"
|
|
22
|
+
# What the rendering left out, stated in the one place that says what exists. Measured need
|
|
23
|
+
# (J25, the user's real ~/Downloads, 2026-08-20): `step test.xlsx` is 18.62 MB and the manifest
|
|
24
|
+
# said `4 parts, 28 rows` — all 28 of those rows are empty lines and the file's actual content
|
|
25
|
+
# is 56 embedded PNGs. Every line here is a COUNT: an adjective ("some images") tells a model
|
|
26
|
+
# nothing it can decide with, and silence tells it something false.
|
|
27
|
+
document_manifest_package_media: "{document}: the file also holds {count} embedded file(s) ({what}) totalling {bytes} bytes, which no row can carry — this reader renders no image or embedded object"
|
|
28
|
+
document_manifest_omitted_media: " NOT in those rows: {count} embedded file(s), {bytes} bytes, anchored to this part"
|
|
29
|
+
document_manifest_omitted_blank: " {count} of those {rows} rows carry no cell value at all and render as an empty line"
|
|
30
|
+
document_manifest_omitted_format: " column(s) {where}: {count} cell(s) store a NUMBER under the date/time format {what} — this reader renders the stored serial number verbatim and does not convert it to a date; convert it with that format code if you need one"
|
|
31
|
+
# J25-D3, the PDF reader. Two silences a page-addressable reader can produce that a
|
|
32
|
+
# spreadsheet cannot: a page that rendered no row (a scan, or glyphs no font maps), and
|
|
33
|
+
# characters dropped because the file states no mapping for them. An empty page and a page
|
|
34
|
+
# nobody could read are different facts and the model must be able to tell them apart.
|
|
35
|
+
document_manifest_omitted_unread_page: " this part rendered NO row: {why}. It draws {count} image(s), {bytes} bytes, that no row can carry — no row means this reader recovered no text from the page, which is NOT the same as the page being empty, and there is no OCR here"
|
|
36
|
+
document_manifest_unread_no_operator: "the page ran no text-showing operator at all, so nothing on it was ever text"
|
|
37
|
+
document_manifest_unread_unmapped: "the page ran {show_ops} text-showing operator(s) and every one of the {unmapped} character(s) they showed came through a font that declares no character map, so this reader dropped them rather than guess at them"
|
|
38
|
+
document_manifest_unread_whitespace: "the page ran {show_ops} text-showing operator(s) and this reader mapped every one of the {vouched} character(s) they showed, and every one of those characters is WHITESPACE — the text was recovered and it carries nothing, which is a fact about the page and not a failure of this reader"
|
|
39
|
+
document_manifest_unread_no_character: "the page ran {show_ops} text-showing operator(s) that put no character on the page at all, neither one this reader could map nor one it had to drop"
|
|
40
|
+
document_manifest_omitted_unmapped: " {count} character(s) shown on this part are NOT in those rows: their codes came from font(s) with no /ToUnicode map ({what}), so the file stores indices into a font's own glyphs and says nowhere which character each glyph draws — this reader drops them rather than guess, because a guess here is indistinguishable from content"
|
|
41
|
+
document_manifest_omitted_other: " NOT in those rows: {count} {subject} ({what})"
|
|
42
|
+
document_page_header: "{document} \"{part}\" rows {start}-{end} of {rows}; each line below begins with its own row number"
|
|
43
|
+
document_page_next: "more rows follow: call document_read again with offset={next_offset}"
|
|
44
|
+
document_page_end: "that was the last row of \"{part}\""
|
|
45
|
+
document_page_truncated: "row {index} was too long for one page and was cut: {dropped} bytes dropped"
|
|
46
|
+
document_unknown: "error: no document named {name}; this task has: {available}"
|
|
47
|
+
document_offset_past_end: "error: offset {offset} is past the end of \"{part}\", which has {rows} rows numbered 0 to {last}"
|
|
48
|
+
document_error: "error: {detail}"
|
|
49
|
+
# `bantamkit_read` (the MCP reader, job43) reuses every `document_*` sentence above with the
|
|
50
|
+
# path standing in for `{document}`. Two sentences are genuinely its own: its continuation
|
|
51
|
+
# line names ITS tool, and a part it does not have is a fact about the file, not the task.
|
|
52
|
+
bantamkit_read_page_next: "more rows follow: call bantamkit_read again with offset={next_offset}"
|
|
53
|
+
bantamkit_read_unknown_part: "error: no part named \"{part}\" in {path}; it has: {available}"
|
|
54
|
+
# The `paste` arm's system message (`docs/eval-data/2026-08-20-document-read-bar.md` §10.2,
|
|
55
|
+
# PASTE_MAX_BYTES = 8621 under Amendment 1, 2026-08-20). The arm registers NO tools, so these four lines are the whole of
|
|
56
|
+
# what the model is told ABOUT the corpus; the rows themselves are docread's own rendering,
|
|
57
|
+
# unprefixed and unreformatted, so `paste` and `document_read` differ in DELIVERY and not in
|
|
58
|
+
# content. Clause 4 makes stating the paste's own completeness part of the contract, and
|
|
59
|
+
# `paste_truncated` says the missing rows are unreachable rather than leaving the model to
|
|
60
|
+
# hunt for a tool that is not there: a paste that lets a truncated head read as the whole
|
|
61
|
+
# sheet is a different, worse arm than the one the bar pre-registered.
|
|
62
|
+
document_paste_preamble: "The following document content is attached to this task. Fields in a row are separated by tabs, rows are given in order, and row 0 of each part is its header."
|
|
63
|
+
document_paste_part: "{document} ({kind}) part {index} \"{part}\": {rows} rows, numbered 0 to {last}; {shown} of them are shown below."
|
|
64
|
+
document_paste_complete: " rows 0 to {last_shown} are shown, which is every row of \"{part}\": this copy is COMPLETE."
|
|
65
|
+
document_paste_truncated: " rows 0 to {last_shown} are shown; rows {first_missing} to {last} of \"{part}\" are NOT shown and no tool is attached that can fetch them. This copy is PARTIAL."
|
|
66
|
+
document_paste_none: " no rows of \"{part}\" are shown and no tool is attached that can fetch them."
|
|
67
|
+
# What the model is told when a tool CALL cannot be made or fails, rather than when a tool
|
|
68
|
+
# answers with its own error (`document_unknown` and friends above are that). It lives here
|
|
69
|
+
# for the reason the layer rule gives and for one measured reason: before 2026-08-20 the
|
|
70
|
+
# dispatcher formatted this sentence itself and interpolated the raw exception, so a model
|
|
71
|
+
# that sent one extra argument was handed
|
|
72
|
+
# `_document_tools.<locals>.list_documents() got an unexpected keyword argument 'document'`
|
|
73
|
+
# — a Python qualname, model-facing, owned by no asset and pinned by no golden. The
|
|
74
|
+
# argument-shaped failures are intercepted before the handler is called now, so `{detail}`
|
|
75
|
+
# carries a handler's OWN sentence and no longer a signature fragment.
|
|
76
|
+
tool_failed: "error: {tool} failed: {detail}. fix the arguments and retry."
|
|
77
|
+
tool_arguments: "error: {tool} does not take the arguments it was given. it takes: {accepts}. fix the arguments and retry."
|
|
78
|
+
tool_arguments_none: "error: {tool} takes no arguments at all. call it with none and retry."
|
|
79
|
+
# 2026-08-20, `RB-P86`. The sibling of the two above, one layer in. `select_declared_arguments`
|
|
80
|
+
# drops keys the schema does not declare and type-checks nothing, so `llama3.2:3b` — which
|
|
81
|
+
# emits a JSON-Schema FRAGMENT as the value of a declared parameter,
|
|
82
|
+
# `{"document": {"description": "stock", "type": "string"}}` — got its dict straight through to
|
|
83
|
+
# `docs.get(name)` and the model read `unhashable type: 'dict'`, thirteen times over 432 graded
|
|
84
|
+
# runs. That is CPython's sentence about a hash table, not a tool's sentence about its own
|
|
85
|
+
# arguments, and a 3b that receives it stops calling tools and answers that it cannot access
|
|
86
|
+
# the workbook. What it is told now names the argument, the type the tool declares and the type
|
|
87
|
+
# it was sent, so the next turn has something to correct. One frame for one or many, because
|
|
88
|
+
# the count is not the thing the model has to act on.
|
|
89
|
+
tool_argument_types: "error: {tool} was called with the wrong type of argument. {problems}. fix the arguments and retry."
|
|
90
|
+
tool_argument_type: "{argument} must be type {expected}, not type {actual}"
|