Concepts
Proposals, stale proposals, partial accepts and the tools an agent gets.
Every Cube component sits on the same core, usecube. You need it directly when you build your own component, when your agent runs on a server, or when you want to know exactly what happens on Accept.
The store
A store holds plain JSON: a sheet's rows, a doc's blocks, a board's cards. People change it directly. Agents can only propose.
snapshot() gives you three things: state, what has been accepted; preview, what you would get by accepting everything; and proposals, the changes waiting for a person.
Proposals
A proposal is a JSON Patch (RFC 6902), a reason and an author. JSON Patch is the format AG-UI uses for state changes, so nothing here is new to a model.
Each proposal is an independent change to the accepted state, like a pull request against main.
Accept and reject
Nothing changes until a person accepts. Rejecting drops the proposal.
Partial accept
A proposal can change many places at once. Pass only to accept or reject the part at some of them; the rest stays pending as the same proposal.
Stale proposals
A proposal remembers what it saw at every place it writes, including "this was empty". If a person changes one of those places first, that part goes stale: an agent never overwrites a person's newer edit. The proposal lists those places in outdated and can't be accepted whole; its other changes still show and can be accepted with only.
One proposal per place
Two pending proposals can't touch the same place. To revise, the agent proposes again with replaces, and the new proposal takes the old one's place. If it forgets, the error tells it how.
Tools
tools() gives any model two tools, named after the store: budget_read and budget_propose. They come in the shape MCP and most SDKs use: { name, description, inputSchema, execute }. The store's about goes into their descriptions, so the model knows where things live.
Tools never throw on a bad proposal. They return { error } in plain English so the model can read it and try again. Calling store.propose() yourself throws a CubeError instead.
Validation
Pass any Standard Schema (Zod, Valibot, ArkType) as schema. A proposal that would break it is refused with the reason, before a person ever sees it.
Rendering
at(pointer) gives one place everything it needs to draw itself: the accepted value, the next value and the proposals touching it. A cell, a block, a card or a form field renders the same way.
Model your state for agents
Give everything an address that never moves. Use keyed objects, not arrays: /rows/r7, not /rows/3. An agent that inserts at index 3 shifts everything after it, and models get indexes wrong. Keep order in a field (order: 2.5) and sort when you render.