Skip to content

3ngram 1.4.2

Choose a tag to compare

@github-actions github-actions released this 12 Aug 14:35
a7f5f60

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: false in the JSON Schema that tools/list publishes.
    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/-32602 the client reads as
    staleness.

    That is the v1.4.1 incident: a session still holding the v1.3.0 catalog called
    get_facts and died on data/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 dropped scope filter reads
    as a scope leak — so no input schema's advertisement moved a byte. The
    briefing/handoff selector union 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: false at 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, so POST /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 = 12 and MAX_PROMPTS = 2 are 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 paginates tools/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-selection eval slice moved from report-only to gated:
    selection_accuracy_at_1 (0.8545) and selection_margin (0.1097) are recorded
    as floors, and max_description_overlap (0.6737, briefing ~ handoff) as a
    ceiling — a metric where lower is better, so eval/fixtures/floors.json gained
    a ceilings block 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.ts threw above both ceilings, which put the argument
    in the one place that cannot measure either metric; the generated
    docs/reference/tools.mdx and prompts.mdx now cite the eval instead.
    AGENTS.md hard rule 8 became an evidence test (JTBD, regenerated surface
    snapshot, per-tool eval scenarios) with its enforcement named.

  • @3ngram/core moves for a comment-only change: write/archive.ts justified
    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