Skip to content

v4.3.0 — release a toolset, and hold the change baseline still

Choose a tag to compare

@princeofscale princeofscale released this 08 Aug 08:46
· 46 commits to main since this release
232c1d6

Two things an agent pays for on every turn: how well it can orient in the place, and what the tool surface costs it. Both measured for the first time this release.

Fewer tokens

The advertised tool list is re-sent on every request, so a loaded toolset is a recurring cost rather than a one-off — and loading was one-way. A session that ran a single playtest carried the runtime domain's ~13.2k tokens of schemas on every later turn whether or not it played again.

load_toolset now accepts unload:

{"unload": ["runtime"]}

It may be sent on its own. Core is never released — dropping it would strand the session with no way to search for or load anything back. Both transports honour it: the stdio server deactivates and re-sends tools/list_changed, and the stateless HTTP transport shrinks the next tools/list.

Measured against the real definitions:

tools ~tokens
Full catalog 218 49.9k
Always-on core 29 4.8k
runtime alone 35 13.2k

tool_catalog_search and load_toolset now report approxTokens per domain, so the agent can see the price before paying it. npm run tools:token-report prints the full breakdown; --check fails when the always-on core set exceeds a 6000-token budget, and runs in release:check and CI — a tool added to the core set taxes every request of every session, including the ones that never call it.

Better orientation in the place

get_changes_since advanced its baseline as a side effect of being read. A snapshotId silently meant "since my previous call" rather than "since the baseline", so asking the same question twice reported an unchanged world — and an agent had no way to ask what it had built over a session, the one question the snapshot id looks like it answers.

The baseline now holds still. Pass rebaseline: true for the previous polling behaviour, where advancing it is the point. New response fields since (baseline / previous-call) and baselineAt say which question was answered and as of when, so a quiet world is distinguishable from a baseline that just moved.

This also fixes roblox://world/changes, where a re-fetch consumed the changes and returned an empty diff with nothing to explain why — a resource read is a read.

Fixed

  • The stdio server changed the advertised tool list even when load_toolset failed. It applied the transition whenever the tool returned, without checking isError, so a partly-valid request answered "error" and expanded the list anyway, leaving the client's view of the tool surface and the server's disagreeing. The Streamable HTTP path had always guarded on isError; both stdio branches now match it.

Compatibility

get_changes_since's default answer is now baseline-relative instead of poll-relative — pass rebaseline: true for the old behaviour. load_toolset's toolsets is optional only when unload is present; existing calls are unaffected. A present-but-malformed toolsets or unload is now rejected rather than coerced to an empty array, which previously reported success for a release that never happened.

Full notes in CHANGELOG.md.