sandtable init drafts one from what a customer hands over during discovery, then leaves a person to review it. Every inferred item stays visible as a TODO or review: pending until someone accepts it.
The whole flow
1
Draft from the API
sandtable/sandtable.yaml, a placeholder policy.md and an empty tasks.yaml. It refuses to overwrite existing files unless you pass --force. It reads OpenAPI 3.x, in YAML or JSON. It writes nothing for a Swagger 2.0 spec (convert it to OpenAPI 3 first) or for a spec it can’t draft a single tool from.2
Add invariants from the SOP
policy.md with the SOP.3
Add tasks from transcripts
4
Review, lint, then run
npx sandtable-ai init --openapi api.yaml --sop policy.md --transcripts calls/ -o sandtable. The steps run in the order above.
From an OpenAPI spec (deterministic)
No model is involved: the same spec always produces the same draft.
It cannot infer some things, so they become
# TODO(review): comments next to the tool:
- 409/422/400/403 responses: encode the condition as a
checkwith awhere. - Side effects in the description (“the load becomes booked”): add the
updateop on the other table. - Action endpoints: replace the placeholder
getwith the ops that model the action.
- header and cookie params
- non-scalar fields (arrays, objects)
-
DELETEoperations -
webhooks, which the API sends to the client rather than serves: OpenAPI 3.1
webhooks, operations’callbacks, and the paths where a client receives events or subscribes to them. A path counts as one only when it has a listener’s shape:- every operation on it is tagged “notification listener” or “events subscription”, and it takes only
POSTorDELETE, or itsPOSTtakes a callback URL (a stringcallbackfield) - it is
/listener/<event>, with<event>a name ending inEvent, and it takes onlyPOST - it is
/hubor/hub/{id}, andPOST /hubtakes a callback URL
/hubs,/listener/restartor/events/{id}/subscriptionsis still drafted. - every operation on it is tagged “notification listener” or “events subscription”, and it takes only
TODO: add seed rows comment that lists the fields seen in the spec.
From an SOP (Claude, grounded)
- Every invariant must quote the SOP. A quote that does not appear verbatim gets a warning.
- Proposals that reference an unknown tool, param, table or field, or that are incomplete, are dropped, and the reason is printed.
- Survivors are appended to the domain (existing comments are kept) with
review: pendingandsource: { file, quote }. - Rules that cannot be checked from tool calls and records (wording, tone, read-backs) are written as soft rule comments, for the planned LLM-judged checks (#16). They are notes, not
soft: trueinvariants.
policy.md when the domain still has the placeholder. Pass --model to choose the drafting model (default claude-opus-5-5).
From call or chat transcripts (Claude, grounded)
- a hidden-goal instruction for a simulated caller who behaves like the real one
- a scripted version of the caller’s turns, so the task also runs offline
- tags, and a new or reused persona
- the end state the world must reach if the agent follows the policy. When the human rep broke the policy, the expectation follows the policy, not the rep.
- PII is scrubbed locally before anything is sent: emails, phone numbers and card-like numbers. Short identifiers such as order or MC numbers are kept. The model is told to replace names and companies with fictional ones.
- When the world lacks rows for the scenario, the draft adds fictional seed rows. A row whose key collides with an existing one drops the task.
- Tasks that would grade nothing (no state and no
calledexpectations), or that fail validation, are dropped with the reason. - Survivors are
review: pending, with the transcript and a verbatim excerpt assource.
Reviewing a draft
Drafted invariants and tasks carryreview: pending and a source (the file and the quote they came from). Tools with review notes are marked the same way.
npx sandtable-ai lint too: it catches drafted tasks that are vacuous, unreachable or unsolvable before anyone spends money running them. To accept an item, change review: pending to review: accepted or delete the line. Review workflow covers what to check.
What a real draft looks like
In a live run on the bundled freight sources (an OpenAPI spec, an SOP and two transcripts, in October 2026, before the example’s SOP grew),init drafted 4 tables and 5 tools with two review notes, 9 invariants that each quote their sentence, and 2 tasks with personas and fictional seed rows. lint then flagged both drafted tasks as not ready, because a side effect the OpenAPI step had marked for review was not modelled yet. Details are on the results page.