Repository navigation
A minor release for agents you can't change, with no breaking changes. run --serve serves each trial's simulated world over REST, MCP and webhooks for an agent that calls its own tools, and sandtable serve grades the conversations a hosted voice platform's own test calls drive. A task's faults make tool calls time out, hit a rate limit or fail, and its events change the world mid-call. sandtable import vapi drafts a domain from a Vapi assistant's export. The freight example gains two fault tasks and a rule, so stored freight runs can't be regraded under it. The plugin's hooks cover more ways of running and installing sandtable.
Added
- Spec: a task's
faultsmake its tool calls fail the way a flaky dependency does: atimeout, arate_limit(a 429 withretry_after) or aserver_error(a 5xxstatus), alatency_msdelay, or both, on the calls of a tool thatcallnames. Withafter_commit: true, a timeout or server error comes after the tool's write landed, the trap where a retry books twice. Invariants check a faulted call as any other, and an invariant can catch the unsafe retry.lintreplays a task'sreferencewith its faults and warns about one the reference never reaches (#15). - Spec: a task's
eventschange the world mid-call, as someone else would: right after thecall-th call of a tool,updatesets fields of a row orcreateadds one, such as another carrier booking a load the agent just found.validatechecks their tools, tables and rows (#15). run --serve [port]: each trial's simulated world is served over HTTP while it runs, for an agent that calls its tools itself. REST atPOST /tools/<name>and at each tool'shttproute, MCP (Streamable HTTP) at/mcp, and a webhook at/webhookthat answers Vapi's and Retell's tool calls. Every call is graded like an in-process one, and a task's faults answer with their HTTP status. Trials run one at a time unless you pass-c, so the base URL means the trial that is running; an HTTP agent receives its trial's own URLs asworldon every request, and a module agent asctx.world. The library exportsstartWorldServerandrunSuitetakesserver(#443).sandtable serve <domain>: serves the simulated world for conversations another system drives, such as a hosted voice platform's own test calls, and grades each one when it ends. A conversation opens with a platform's call id, an MCPinitialize,POST /sessionsor the first request, and ends with Vapi's end-of-call report, Retell'scall_ended,POST /sessions/<id>/endor an MCPDELETE. It is graded on the end state, the invariants, the tools called and, with the transcript, what the agent said.results.jsonandreport.htmlare rewritten after each conversation, and Ctrl-C prints the summary. Withrun --serve, this closes black-box mode (#444, #14).- Spec: a tool's optional
http: { method, path }is its route in the customer's API, such asGET /loads/{load_id}, served byrun --servewith arguments from the path, query and body.init --openapidrafts it for each operation, and the freight example has it for the five tools its API defines (#443). results.json: a tool call that came overrun --serve's world server hasvia(rest,mcporwebhook), which the HTML report shows (#443).results.json: a tool event that a fault hit hasfault(error,status,retryAfter,latencyMs, andcommittedfor anafter_commitfault), and one that a task's event followed haseventChanges. The HTML report shows both on the call. The library exports theFault,TaskEventandFaultAppliedtypes (#15).sandtable import vapi <path>: drafts a domain from a Vapi assistant's export, with no model call. The system prompt becomespolicy.md, the function,transferCallandendCalltools become tools, and each simulation scenario becomes a task. A scenario's tool mocks become that task's seed rows and expected calls, and its variable values itsgiven.context. Everything is pending review, with notes on what the config can't say.examples/vapiin the repository is a fictional export to try it on (#34).
Changed
- Roadmap:
ROADMAP.mdand sandtable.dev/roadmap list what 1.2.0 ships, under In 1.2, instead of the black-box milestone under Next, and say that nothing is planned as Next yet. The comparison pages no longer call serving the world, faults and importing Vapi configs planned (#451). examples/freight: two tasks with faults,booking-times-out-after-it-landsandsearch-rate-limited. There is a new invariant,book-available-load, which catches an agent that books a load again after a booking timed out though it went through. A new policy line says what to do when a tool fails, and the scripted agents handle it:carefullooks the load up before booking again and searches again after a rate limit, andnaivebooks again. The example now has 52 tasks and 20 invariants, and the naive agent scores pass^1 9.6%. Becausepolicy.mdand the invariants changed,regradeandleaderboardresume refuse stored runs of every freight task, andrun --baselineagainst an older freight run labels every task changed (#15).- Plugin: the drafting skill and the plugin guide say that pnpm installs only releases at least a day old by default, so on the day of a release
pnpm add -D sandtable-aiinstalls the one before, which is older than the skills describe. The skill has Claude comparenpx sandtable-ai --versionwith the latest release, say so when it's older and use what that version's--helplists; the skill and the guide givepnpm add -D sandtable-ai@<version>, andminimumReleaseAgeExcludefor a project that sets its own age, for getting the newest (#433).
Fixed
- Plugin: the install hook now stops the unrelated npm package named
sandtablefrom being installed or run in sandtable's place. Asked to "install sandtable", Claude had runpnpm add -D sandtable, which installs another project. An install ofsandtablewith npm, pnpm or yarn is held, with the same command forsandtable-aito run instead. Runningsandtablethrough npx is held where no sandtable-ai is installed, since npx would fetch the other package (#441). - Plugin: the plan hook now resolves the relative paths of
npm run sandtable -- initfrom the package root, where npm runs the script, instead of from the shell's folder. In a project with asandtablescript, a paid run from a subfolder now gets the plan of a dry run of the same files, and no longer gets the plan of a dry run that read other files. The root is the nearest folder with apackage.jsonor anode_modulesfolder, or the one--prefixnames, as for npm; for a workspace's script (-w), the plan isn't known (#430). - Plugin: the plan hook now catches a model-backed
initrun asnpm run sandtable --silent -- init,npm run sandtable -C .. -- initor with other npm options between the script's name and the--. Before, such a run had no dry-run check and no plan message (#439).
What's Changed
Other changes
- fix(plugin): the plan hook resolves npm run paths from the package root by @Uliseis in #436
- docs(plugin): pnpm installs a sandtable-ai at least a day old by @Uliseis in #438
- fix(plugin): the plan hook catches npm run sandtable with npm options before -- by @Uliseis in #440
- feat(spec): faults and events for tasks: injected tool failures and mid-call world changes by @Uliseis in #442
- feat(run): serve each trial's world over REST, MCP and webhooks with run --serve by @Uliseis in #445
- feat(cli): sandtable serve grades conversations a hosted platform drives by @Uliseis in #446
- feat(init): sandtable import vapi drafts a domain from a Vapi assistant's export by @Uliseis in #447
- fix(plugin): the install hook stops the unrelated sandtable package from being installed or run by @Uliseis in #448
- docs(docs): the roadmap lists what 1.2.0 ships by @Uliseis in #452
- chore(release): v1.2.0 by @Uliseis in #453
Full Changelog: v1.1.3...v1.2.0