3ngram 1.4.2
3ngram v1.4.2
Patch release: the advertised MCP tool output schemas open up, so a client
holding a cached catalog no longer hard-fails on a response field added after it
fetched. Runtime validation is untouched — the server still parses its own
results strict — and inputs stay closed. Also in this release, the self-imposed
MCP surface caps are replaced by a gated eval that measures what they were
standing in for.
Changes
-
Tool output schemas advertise
additionalProperties: true(issue #154).
Every tool output object was.strict(), which Zod 4 emits as
additionalProperties: falsein the JSON Schema thattools/listpublishes.
Clients cache that catalog for an hour, so any release adding a response
field hard-failed every validating client for the whole TTL window — and
nothing prompted an early re-fetch, because the failure is client-side
output validation rather than a-32601/-32602the client reads as
staleness.That is the v1.4.1 incident: a session still holding the v1.3.0 catalog called
get_factsand died ondata/facts/0 must NOT have additional properties—
a nested item object, a purely additive field. Every object node reachable in
all eleven tool output trees now carries.meta({ additionalProperties: true }):
root envelopes, array item objects, union members, and section wrappers alike,
since the incident failed at depth rather than at the root.The runtime contract does not move.
.meta()carries metadata only — the
objects remain.strict(), so the server still rejects an unknown key in a
result it produced itself, and the search envelope's projection-homogeneity
refinement still relies on its hit members being strict..loose()and
.passthrough()would have moved the runtime contract and were deliberately
not used.Inputs stay closed, and the asymmetry is pinned by a test. An unknown
argument key remains a loud rejection — a silently droppedscopefilter reads
as a scope leak — so no input schema's advertisement moved a byte. The
briefing/handoffselectorunion is the one object reachable from both an
input and an output tree (it is an argument and an echo); the openness rides
an output-side derivation, leaving the input union untouched. A registry
invariant test walks every emitted output schema asserting no
additionalProperties: falseat any depth, and asserts every input root still
carries it.The OpenAPI response schemas open up too, deliberately. The generator
reuses the same output schemas for REST responses, soPOST /api/v1/memories,POST /api/v1/search,GET /api/v1/facts,
GET /api/v1/briefing, and the revise/resolve responses now publish
additionalProperties: true. Same rationale: a REST reader compiled against an
older spec should not break on a field that was only added. Request bodies and
query schemas are untouched. -
MAX_TOOLS = 12andMAX_PROMPTS = 2are gone; a gated eval replaces
them (#155). Both were 3ngram's own discipline, entered at the v1.0.0 launch
commit and sourced to nothing upstream — the specification defines no maximum
tool count and paginatestools/list, and the pinned SDK enforces no limit.
Neither constant was ever exported past its module and neither appears in the
public API report, so there is no runtime or API change for any client: the
server still registers exactly 11 tools and 2 prompts, with the same names,
schemas, and annotations.What replaced them measures the property they proxied for. The
tool-selectioneval slice moved from report-only to gated:
selection_accuracy_at_1(0.8545) andselection_margin(0.1097) are recorded
as floors, andmax_description_overlap(0.6737,briefing~handoff) as a
ceiling — a metric where lower is better, soeval/fixtures/floors.jsongained
aceilingsblock with its own comparison rather than storing an inverted
floor. A twelfth tool is no longer blocked; a twelfth tool whose description
reads like an existing one now fails a required check, and the failure names
the offending pair.The docs generator no longer arbitrates the surface —
generate-mcp-reference.tsthrew above both ceilings, which put the argument
in the one place that cannot measure either metric; the generated
docs/reference/tools.mdxandprompts.mdxnow cite the eval instead.
AGENTS.mdhard rule 8 became an evidence test (JTBD, regenerated surface
snapshot, per-tool eval scenarios) with its enforcement named. -
@3ngram/coremoves for a comment-only change:write/archive.tsjustified
its REST-only surface by citing hard rule 8 without its reason, which no longer
reads correctly now that the rule is an evidence test rather than a count. No
behaviour changed in core.
Upgrading
No configuration or migration changes, and no breaking API changes.
Clients that validate MCP tool results against the advertised schema will accept
additive response fields from now on, including across a stale catalog window.
A client pinned to a locally vendored copy of an older schema should refresh it
to pick up the openness; nothing else is required. Because the change is to what
the server advertises and not to what it accepts, servers and clients can be
upgraded in either order.
REST consumers that generate types from the OpenAPI document will see
additionalProperties: true on response schemas. Strict code generators may emit
an index signature on those types where they previously emitted a closed object.
Library versions
@3ngram/core 0.9.2, @3ngram/db 0.8.2, @3ngram/schema 0.7.2,
@3ngram/config 0.2.6, @3ngram/llm 0.2.4.
Immutable container reference
ghcr.io/b3dmar/3ngram@sha256:7b009a52bb94a7100054556ea8a907381ec1e5dbab91af10bba32bec89732615