# Quiver

> Every AI program at Bigspin in one place: built, run, watched, rated, improved, deployed, and called by the rest of Bigspin. A **program** is a FunctAI program (TypeScript, in `groups/<group>/<program>/`): an AI function, a pipeline, an agent. Every version of one is a **board** (id `board:<sha256>` of everything that decides what runs). A program is **deployed** when one of its versions answers at an **address** (`poser/study`): where the app calls it, keeping a promise there (the inputs it takes, the outputs it gives, the notes callers see while it runs). An address belongs to no program: any program of its group that keeps its promise may be deployed there, under the deploy rule; changing the promise needs another address. A **group** (`poser`) is a folder of related programs and what they share: named types (`types.ts`, `types.json`: `Insight`), how they are shown (recipes), its members (each with a role; owners among them), and its policy in its `group.json`: what its programs may reach and its daily budget, which only its owner changes (a secret that is not a model provider's key: only an admin).

Quiver holds Bigspin's AI programs: FunctAI programs in groups/<group>/<program>/ (an AI function, a pipeline, an agent), every frozen version of one a board. A program is deployed when one of its versions answers at an address (poser/study): where the app calls it, keeping a promise there (the inputs it takes, the outputs it gives, the notes callers see while it runs). An address belongs to no program: any program of its group that keeps its promise may be deployed there, and compete for it; changing the promise needs another address (poser/study/v2). A group (poser) is a folder of related programs and what they share: named types, a display, an owner, what its programs may reach. Every program, deployed or not, can be called, tested and judged by the team (/p/<program>: its newest version); services call addresses only (/a/<address>). Typical work: find what exists (search by what things take and give: returns:Insight, takes:url); show a program, address, version, group or run; check, then publish a program from a workspace (or change content with the files tool); run or test a program, an address or a version; reply to a run (run with continue: its conversation's next turn, shown the turns before it); edit a version's data into a new version; deploy a program or a version to an address. Every run is a turn of a conversation, so a program needs no history or context input to remember its own earlier answers: continue the run instead. A program that needs its model to run code, read or write files, or browse the web uses Quiver's shared tools (the tools tool: bash, python, r with ggplot2, read_file, write_file, edit_file, browse), never a tool written again: they keep to the task's workspace, hold no secret, stop on time, and show the model the pictures it made. In code: import one from shared/tools/ and pass tools: [it]; without code: create with definition.tools: [names]. stop stops a run or a tool trial by its id, at once (no preview), whenever one runs longer or costs more than it should. Before building a wrapper to fit an address, check whether the program already keeps its promise (check or show: sameInterface); if it does, deploy or compare it as it is. To write code (a pipeline, an agent), work in a workspace: quiver pull <group | program> [--shared] --into DIR, edit with your own tools, then check and publish from inside DIR (or quiver mcp there). To make a program better without writing it yourself: improve (GEPA rewrites an AI function's instruction from its training cases; the result is a draft: test it on held-out cases, then promote it, then deploy it). Tools that change something preview first: call them with confirm: false (the default), read what would happen, then call again with confirm: true and a fresh request_id. How good a program is: measures (every version's score by every source, each with its 95% range, each compared case by case with the version live: better, worse, the same, not worse, or can't tell yet). It rests on its cases (the cases tool: data beside its versions; held-out ones decide deploys), on judges (exact match where a case knows its right answer; judge programs its measures.json names) and on people (rate, prefer: only as your person tells you). Report a score with its range and its cases; never call "can't tell" "no difference". To make a program better, add the cases where it fails (cases with from_run; measures lists the cases where versions disagree), then test. Runs and tests call real models and cost money. Never deploy to an address someone else's callers rely on without saying so to your user.

## Use it from an agent

- MCP, remote (no files: the `files` tool changes content directly): `https://quiver.staging.bigspin.ai/mcp` (Streamable HTTP); it asks your person to sign in once (OAuth), then acts as them. Local, in a workspace: `quiver mcp` (stdio).
- Command line, on macOS or Linux (it brings its own Node.js when the computer has none recent enough): `curl -fsSL https://quiver.staging.bigspin.ai/install.sh | sh`, then `quiver login` (a code your person approves on Quiver's page) and `quiver help`. Writing a program's code works from anywhere, in a workspace (*Write a program's code*).
- Who you are: your person, through their key (`Authorization: Bearer qk_…`: `quiver login` makes one) or their sign-in (MCP's OAuth): you may do what they may, in each group by their role (viewer, tester, editor, approver, owner; the `people` tool, me, says which). A service's key calls addresses only. Runs are capped by what they cost a day: a key's, a person's, a group's.

## Before you build: does it already fit?

To put a program at an address, or to compare it with what is live there, first ask whether it already keeps the address's promise (the same inputs, outputs and notes callers see): `quiver check <program>` (or `show <program>`) lists its group's addresses with `sameInterface`. When it does, there is nothing to build: deploy it (the deploy rule compares it with what is live there), or run both on the same inputs. Write a wrapper pipeline only when the inputs or outputs really differ, and only the steps that make them the same. Read a program's code before you wrap it: it may already do what you were about to add.

## Write a program's code (from anywhere, with any coding agent)

Quiver's content (every group's files, programs' code and data, the shared tools) is kept by Quiver alone, never in git. A pipeline, an agent with tools of its own, any code: in a **workspace**, a working copy of some of it that you edit with your own editor or agent, then check, publish, push.

1. `quiver pull <group> --into work` or `quiver pull <program>` (the group's files: its programs, types, helpers, `group.json`, `README.md`; and `shared/`, which its programs import), `quiver pull shared` (the shared tools and helpers; Quiver's toolkit, read-only). Several pulls into one folder add up; `quiver pull` again brings the latest, merged with your changes. The folder is marked `.quiver.json` (its server, the commit it was pulled at).
2. Read and edit with your own tools. A new program is a folder `groups/<group>/<name>/` with `program.ts` (exports `build()` and a default program; FunctAI TypeScript, `import { ai, module, t } from "functai"`) and usually `program.json` (its data: prompts, models). It may import other programs of its group (`../insight_walker/program.ts`: it then uses them as steps) and `../../../shared/…`; imports stay inside `groups/` and `shared/`.
3. From inside the folder: `quiver check <program>` (builds it in its sandbox: what version it would be, its interface, which addresses it fits; changes nothing), then `quiver publish <program>` (its files committed to content, merged with what changed since your pull, then published), `quiver run <program> --input '{…}'` or `quiver test <program>` (both publish this folder's files first), and `quiver deploy` (then an approval). `quiver status`, `quiver diff`: your changes; `quiver push -m WHY`: commit changes that are not a program's (a README, a shared tool). The same lines changed by someone since come back with conflict markers (`<<<<<<< yours` … `>>>>>>> latest`): settle them, then push again. `quiver mcp` from inside it gives an agent the same, as MCP tools.

`check` builds the program, so it catches what fails to load (a wrong import, a missing file, a shape FunctAI refuses); it does not type-check TypeScript. Publishing code needs the editor role in its group.

## Measure a program: cases, judges, people, honest ranges

How good a program is, and whether a change made it better, is measured the same way for every program (FunctAI's measures, contract measures.md), from three kinds of evidence:

- **Cases**: what a program is measured on, data beside its versions (adding or correcting one makes no new version). Each is inputs it can be asked again (a conversation's turn: with the turns it was shown), and what is known right of its answer (`expected`), or nothing when only judges and people can say. **Held out** cases decide deploys and are never shown to an optimizer. `quiver cases <program>`; `--add FILE` (an examples.jsonl), `--from-run RUN [--right]` (what a run was asked; its answer right). In a workspace: the program's examples.jsonl, written from Quiver's on each pull and taken in when it is published.
- **Results**: every answer a version gave, on a case (its tests', its deploys' checks', its live calls'). The same version on the same case is reused, never paid for twice: a test asks only what is missing.
- **Ratings**: who said what. A **judge** rates automatically: exact match, where a case knows its right answer; or a **judge program** its `measures.json` names (beside its program.json: `{"criteria": [{"name": "Insight", "judge": "insight_judge"}], "deploy": {"margin": 0.05}}`), an ordinary Quiver program given, by name, the case's inputs, `answer`, `outputs`, `expected`, `item`. **People** rate on the pages, item by item (their group's question of a named type) or whole answers (right, wrong), and pick between two answers side by side, blind. Only people's **fair** verdicts (the judge page's fair mode: a random draw, blind, in order) measure without the bias of choosing what to judge, and only they say whether a judge can be trusted.

Choosing between versions on cost, speed and score at once: `quiver tradeoffs <program> [rival …] [--steps] [--by time] [--same-cases]` (the `tradeoffs` tool; the Trade-offs page): each version's mean cost and median time per answer, its score with its range, the line of the versions nothing cheaper beats, and whether each step up the line is surely better (case by case) or can't tell yet. Read it with `quiver measures <program>` (the `measures` tool): every version's score by every source, each with its 95% range and the cases it rests on; each version compared case by case with the one live: **better**, **worse**, **the same**, **not worse**, or **can't tell yet** (the cases cannot rule out a difference: say so, never "no difference"). When you report a score, give its range and how many cases it rests on. Do not rate as a person unless your person tells you their verdict: an agent's own judgement belongs in a judge program.

**The deploy rule's score check** uses the same: both versions answer the held-out cases, the deciding judge rates them, and they are compared within the program's margin. Passed, or unclear (said plainly), the deploy waits for an approval; **worse**, it goes live only with a reason (an override, kept with it: never give one your person did not).

## Tools (MCP), the same operations as the command line and the API

- **find** (reads only): Search what Quiver holds: programs (FunctAI programs, deployed or not), addresses (where the app calls a deployed program), named types (Insight, Persona), groups, boards (frozen versions of a program) and runs. Words match names and descriptions; filters match what things take and give: returns:Insight, takes:url, model:gpt-6-luna, group:poser, kind:agent (or pipeline, function). Empty query: the latest of each.
- **show** (reads only): One thing, whole: a program (its versions, where it is deployed or could be, its examples, its latest test), an address (its promise, the version answering there, every deploy, its former names), a group (its programs, addresses, named types), a run (its outputs, cost, steps, and the conversation it is a turn of: its turns so far), a board (a version: its interface and notes, steps and models, its setup, what it may reach, files, addresses), or a deploy.
- **check** (reads only): Before publishing: build a program from the workspace and say what board it would be (its id, its group, whether it is new, data or code, its interface, its named types), and to which of its group's addresses it could be deployed (the same promise). Runs nothing, changes nothing. Needs Quiver's local MCP server (quiver mcp) in a workspace (quiver pull).
- **publish**: Publish a program from the workspace: its files are committed to Quiver's content first (made from the commit the workspace was pulled at, merged with whatever changed since, refused where the same lines changed: pull, settle, publish again), then its version is built from content as it now is, frozen under the board's id, callable at once (/p/<program> is now it; /b/<board> always is) and ready to deploy. It becomes the program's latest. replace: true makes these files win over what changed since (the history keeps the rest). Needs Quiver's local MCP server (quiver mcp) in a workspace (quiver pull).
- **run**: Run a program (persona_maker: its newest version), an address where one is deployed (poser/study: the version there now) or a version by its board id (board:…) on inputs, with real models and services (it costs money). Waits for the result (up to wait_seconds), else answers with the run's id to show later (stop stops it). Every run is a turn of a conversation, so any run can be replied to: give continue (a run's id) and the next inputs, and the program is shown that run and every turn before it, as a real back-and-forth, and answers in the same conversation. Continuing a run that was already continued makes a branch; nothing is lost. A program never needs a history or context input to remember its own earlier answers.
- **test**: Test a program (persona_maker: its newest version), an address where one is deployed (poser/study: the version there now) or a version by its board id (board:…) on its program's cases (data beside its versions: the cases tool; in a workspace, its examples.jsonl): its held-out cases by default, never used to improve it. Each case is asked (an answer this very version already gave is reused, not paid for again), then judged: exact match where the case knows its right answer, and each judge program its measures.json names. The answer is each criterion's score with its 95% range, by each source, and the cases it got wrong. Real models: it costs money.
- **edit**: Make a new board from a board's data, without code: a JSON Merge Patch (RFC 7396) of one of its programs' program.json (instructions, model, settings). What a board may reach is declared in its code (needs), so an edit never changes it. The new board can then be deployed. A program's cases are not part of a version: change them with the cases tool.
- **deploy**: Deploy a program (its newest version) or one version (board) to an address, where the app calls it. A new address (group/name, or only the name: its group is the program's) is made, and what the version takes and gives becomes its promise. An existing address moves to it under the deploy rule: the same promise (inputs, outputs, notes callers see; refused otherwise: deploy to another address); then the score check: both versions answer the program's held-out cases, its judge rates them, and they are compared case by case within its margin (measures.json deploy.margin): passed (better, the same, not worse), unclear (the cases cannot tell: said plainly), or worse; then it waits for an approval, always, a prompt or model change too (anyone may give it, the one who asked too). A version measured worse is approved only with a reason (approve with reason: an override, kept with the deploy); never give one without your user asking you to, and give theirs. An address belongs to no program: any program of its group that keeps its promise may be deployed there (an agent in place of a pipeline). Skipping the score check (skip_check: why, with a new deploy, or with skip: a deploy id still checking) is dangerous: the version goes live unmeasured once approved; never skip it without your user asking you to, and give their reason. One request at a time for a version at an address: if someone already asked for that version there, the preview says so (asked: their request) and deploying again is refused (already_asked): approve theirs instead (if your user may: may_approve), never ask again. Requests for other versions may wait side by side (alongside): whichever is approved first goes live, and the others are then checked again against it, by themselves (their checks say rechecked). Also: approve or withdraw a deploy waiting (an approval refused with address_moved was checked again: read its checks, then approve again); rename an address (rename_to: its old name stays, so its callers still arrive). The preview says what each check finds.
- **create**: Make, from a definition alone (no code, shared/ai-definition.ts), either a program or a named type in a group. A program (kind program) is an AI function: what it does (description: the model's instruction), its inputs, its outputs (one named result is the answer itself; several: the last is the answer), its model (lm). A named type (kind type) is a record: an optional description, and its members (fields). A program may also be given shared tools by name (definition.tools: ["r", "read_file"], max_steps: how many model calls at most, default 12; see the tools tool): it is then an agent. Field kinds: text, number, integer, boolean, choice (with choices: [{value, meaning?}]), list (of text), image and images (a program's inputs only), and type or types (one of the group's named types, or a list of them, with type: "Lead"; a type's members may name only types made this way). An input may be optional; a member marked optional may be empty (null). A program is built in its sandbox first, then groups/<group>/<name>/ is committed to Quiver's content and its first version published (/p/<name>); a type is added to groups/<group>/types.json, committed. The preview writes nothing. A program that should hold a conversation (a chat, a back-and-forth) needs no history or context input: every run can be continued (the run tool's continue), and each turn is shown the turns before it; an input is for material to work on (a thread to answer), as a typed list rather than a block of text.
- **group**: Make a group (related programs: groups/<name>/, its group.json and README.md, committed to Quiver's content; you become its owner), change its title or description (its owner), or change its name (rename_to) while nothing uses it (no version published, no address, no group importing from it: its name starts every one of its addresses). A new group's programs may use the model providers named (models) and nothing else; other services are added to its group.json (files tool, or a workspace).
- **recipe**: How Quiver's pages show a named type's values, as data: its recipe (kept in Quiver, every save a new version, the newest shown; a type without one is shown as plain data). Without a recipe: the type's recipe now, every version, what Quiver proposes for it, a recent value of it, and the format. With a recipe: checked against the type (every problem said), then saved with confirm: true; the same recipe as now is no new version, and going back is saving an older one again. A recipe says how Quiver's pages show a named type's values: { "recipe": 1, "title"?: template, "cites"?: expression, "media"?: [...], "brief"?: { keep?, drop? }, "show": [pieces] }.

Expressions: a field (headline), a path (views.0.title; views.*.id for every item), the whole answer ($answer.people), the value itself ($), quoted text ('—'), numbers, helpers (a helper's result may be followed by a path: lookup(authorId, $answer.people).name). Templates: text with {expressions} ({{ for a brace). Helpers: count (how many items a list has); first (a list's first item); join (a list as text, joined by ', ' or the text given); site (a web address's site (airbnb.com)); path (a web address's path and query (/pricing?x=1)); lookup (lookup(id, list, 'id'): the item of a list whose field (id) is the id given); or (the first value that is not empty); if (if(value, then, otherwise): then when the value is not empty, else otherwise (or nothing)); number (a number, with this many digits); percent (a fraction (0.42) as a percentage (42%)); money (dollars ($1.25)); duration (seconds as a duration (2 min 5 s)); date (a date and time, as people read it).

Every piece may also have: compact ("hide": not in lists and side by side; "only"), when (an expression: shown only when not empty), label (inside fields and table). Pieces:
- stack: pieces one under another. Takes show (needs show).
- row: pieces side by side, wrapping. Takes show (needs show).
- fields: label on the left, a piece on the right; each row is a piece with a label (text → text, value → value, or name its piece). Takes rows (needs rows).
- section: a heading with pieces under it, maybe folded. Takes title, show, folded (needs show).
- repeat: its pieces once per item of a list ($ is the item). Takes items, as, show (needs items, show).
- table: a list of records as rows; each column is a piece with a label, per item. Takes items, columns (needs items, columns).
- heading: a title. Takes text (needs text).
- text: text (Markdown when markdown: true), shortened to `clamp` lines. Takes text, markdown, clamp, muted (needs text).
- line: a short line with an icon (strong: bold, as a name). Takes text, icon, strong (needs text).
- badge: a tag, coloured by its value (colors: {value: green|amber|red|blue|gray}). Takes value, text, colors.
- value: a value, formatted (auto, text, number, percent of a fraction, money, duration in seconds, date, bytes, yesno). Takes value, format, digits (needs value).
- list: a list of plain values. Takes value, as (needs value).
- score: a number drawn as a bar on its scale. Takes value, min, max (needs value).
- link: a web address: shown in full, as its site, or as its path. Takes value, as, text (needs value).
- picture: a picture (a Quiver file key or a web address); open: the media id the viewer opens. Takes source, caption, open (needs source).
- video: a video player; start: second to start at; name: what a timeline seeks. Takes source, start, name, caption (needs source).
- timeline: timestamped moments; clicking one seeks the named video ($ is the moment; at: seconds, or a date with `from` the video's start). Takes items, at, from, text, video (needs items, at, text).
- media-chips: buttons that open the pictures or videos the value cites, by id. Takes value (needs value).
- file: a link to download a file. Takes source, text (needs source).
- code: code or raw data, in a fixed-width font. Takes value (needs value).
- nested: a value of another named type, drawn by that type's own recipe (plain data when it has none). Takes value (needs value).

media: the pictures and videos a value holds, each { from?: the list they are in, id, picture | video: a Quiver file key (sha256:…) or a web address, caption?, detail?, label?: short, for chips }; other values cite them by id (cites, media-chips). brief: what of the whole answer is sent with each value of this type to judge (keep: top-level fields; drop: paths, * for every item). A value of another named type: the nested piece draws it by that type's own recipe. Icons: person, people, link, image, video, clock, calendar, tag, alert, check, cross, info, star, page, money, chat, mail, map, code, search. Colours: green, amber, red, blue, gray.
- **forget**: Forget, for good, the conversation a run is a turn of: every turn's inputs and answers, FunctAI's records of their calls, what callers saw, the files they made and the pictures they held (unless another run holds the same picture), and their ratings. What stays of each turn is that it ran: when, how long, what it cost, on which version. For a person's request to erase what they said. Not while a turn of it runs. The preview says how many turns it would forget.
- **models**: The models programs may use: each model provider whose key this server holds (OpenAI, Anthropic, TypeSafe, Google, xAI, OpenRouter, Groq… whatever lm15 reaches with a key), the models its key opens, what each costs per million tokens and where that price comes from, and which groups may use it. No argument: the providers, in a line each. provider: its models (text models answer any FunctAI function; TypeSafe's Jev answers judgments only: choices, yes or no, scores, with their probabilities). group: the models that group's programs may be given, each allowed or not and why (name one as a program's lm, exactly as listed: `gpt-6-luna`, `jev-latest`, `openai:ft:gpt-4o-mini…`). refresh (with provider): ask the provider again which models its key opens. price (confirm): set a model's price where none is known (a fine-tuned model), with where it comes from; null prices clear it. allow (confirm): let a group's programs use a provider, or no longer (allow: false); only the group's owner may. Keys are never handled here: a person adds one on the Models page or with `quiver models key <provider>` (a key given to you would be in your transcript). Never ask your person to paste a key into the conversation.
- **tools**: The library of shared tools: tools any program may give its models, written once in quiver/shared/tools/ with the care a tool needs (a workspace it keeps to, no secret, a time limit, pictures shown to the model and kept for people). bash, python and r run code in the task's workspace (R with ggplot2: a chart saved as PNG is shown to the model); read_file, write_file and edit_file work on files there; browse hands a task on the web to Browser Use's agent. Use them before writing a tool of your own. No name: every tool, in a line each. A name: that tool whole (its inputs, effects, what it reaches, the line that imports it, who uses it; with_code: its code too). A name and try: run it once on that input, as a model would, behind a wall of what it declares (browse spends real money, up to its cap): every trial is kept, with its cost, which counts toward your key's daily cap; one still running after wait_seconds is answered with its id, and stop stops it. Give tools to a program: in code, `import { r } from "../../../shared/tools/r.ts"` and `tools: [r]` on the ai() function (maxSteps for how many steps); without code, `create` with definition.tools: ["r", "read_file"]. Add a tool: a file shared/tools/<name>.ts exporting a FunctAI tool(...) (helpers in files starting with _).
- **improve**: Improve a program (persona_maker: its newest version), an address where one is deployed (poser/study: the version there now) or a version by its board id (board:…) with an optimizer: GEPA (FunctAI's) has a teacher model rewrite the program's instruction from its mistakes on its training cases (half to learn from, where the teacher sees its answers; half to choose with, scores only). Held-out cases are never sent: they decide deploys. What it chooses becomes a draft version: not the program's latest, live nowhere. Preview first (the plan: its cases, settings, a rough cost; every check made, nothing spent), then call again with confirm: true and a request_id. It answers with every instruction it tried and its score, and the draft; then test the draft on held-out cases (test, target: the draft's board id), and make it the latest with promote: the draft's board id (then deploy it as any version: checked and approved). Needs an AI function whose program.json reaches FunctAI's instructions (programs made from the pages do; others spread instructionsOf(data)), and at least 4 training cases with known answers (20 or more to trust what it picks). Costs money (the program's calls and the teacher's), stopped at max_usd.
- **stop**: Stop a run (a program's run, test or call, by the id run and test answer with) or a tool trial (by the id tools answers with) while it runs: its model calls are cancelled, what its code opened is closed (a Browser Use run is cancelled; what it cost until then is kept), and its sandbox is ended a few seconds later whatever it does. Acts at once, without a preview: stopping is what Stop is for, and nothing is lost (what it did so far is kept). One that has ended is answered as it is, so it is safe to repeat.
- **rate**: A person's verdict on a run's answer, kept as FunctAI's rating (what people's scores, judges' agreement with people and deploys read). One item of it: its group's question of that named type (group.json judge: Poser asks of each Insight "Is this a real problem?"), yes, no or unsure; or the whole answer: right or wrong, with what it should have been when you know (correction). Your latest verdict on it replaces your earlier one; null withdraws it. You rate as your user, only when they tell you their verdict: an agent's own judgement belongs to a judge program (measures.json), never here.
- **prefer**: A person's pick between two answers to the same inputs (side by side): a and b are what answered, in the order they were shown (run:<id>, or call:<id> for a step's call inside a run); winner a, b, tie, or null to withdraw. Picks rank versions (Bradley–Terry) and say whether a change helped. Show your user the two answers without saying which version made which (blind: true), in an order you did not choose; record only their pick.
- **cases**: A program's cases: what it is measured on (tests, deploys' checks), data beside its versions, so changing them makes no new version. Each is inputs it can be asked again, what is known right of its answer (expected: values of its outputs; null when only judges and people can say), and its side (held_out: what deploys are checked on, never shown to an optimizer; train). With nothing else: list them. add: new cases ([{inputs, expected?, split?}], or examples.jsonl text). from_run (or from_call, a step's call inside a run): a case from what it was asked, expected "answer" when its answer was right. case: one by id, to correct (expected, split, note) or retire (retire: true; false brings it back). In a workspace, a program's cases are its examples.jsonl, written from Quiver's on each pull and taken in on publish.
- **measures** (reads only): How good each version of a program is, and how far to trust it: its criteria (questions its answers are judged by), each version's score by each source (people; people's random draw; each judge; each judge combined with the draw), every score with its 95% range and the cases it rests on; each version compared case by case with the one live (better, worse, the same, not worse, or can't tell yet); side-by-side win rates and a ranking; whether its judges agree with people; the cases where versions disagree (worth adding as cases). Read it before deploying, and say its ranges when you report a score.
- **tradeoffs** (reads only): Choosing what to deploy: each version of the programs named (and, with steps, each step inside their runs) by what an answer costs (mean), how long it takes (median, and slow: 90th percentile) and its score with its 95% range, and the line of the versions nothing cheaper (by: time, faster) scores as high. Each point's standing in words: on the line, and whether it is surely better than the cheaper one on it; or below it, compared case by case with the version on the line at its cost (surely worse, or can't tell yet). Programs that take and give the same things are compared together (together: every program named, on the inputs they all take); a point not judged yet, or without a price, is said to be, never given a number. same_cases: compare on the cases every judged version answered. Say the ranges and case counts when you report it.
- **judge**: Have a program's judge programs (named in its measures.json: criteria with a judge) rate answers it already gave, without asking it again: a random draw of its finished runs (draw: how many; the same for the same seed), or runs by id. Their ratings stand beside people's: a judge is trusted as far as it agrees with people (measures: judges). Real models: it costs money. Or keep a judging already made (from_run: a judge program's finished run whose answer rates the one run in runs, e.g. an experiment's grading): nothing is asked again, the rating names that judge run's program and the call that judged; field: which of its outputs is the value (default its answer).
- **files**: Quiver's content (every group's files: group.json, README, types, programs' code and program.json, measures.json; and shared/: the shared tools and helpers), kept by Quiver alone, with its history. list: the paths (under prefix) and the current commit. read: files' text (paths or prefix). write: change files ({ path: text, or null to remove }), made from `base` (the commit you read them at): merged with whatever changed since, refused where the same lines changed (then read again and rewrite). log: the history (of a path). show: one commit's changes. revert: undo a commit. Quiver's own toolkit (shared/ai-function.ts, …) is read-only. A change to a program's files is content; publishing it as a version is the publish tool's (or create, or edit).
- **people**: Who may do what. me: who you are to this Quiver (your roles in each group, your budget). members: who has a role in a group. grant: give someone (their login: an email, a tailnet login, a link's name; before they ever sign in too) a role in a group: viewer (sees), tester (runs, rates, adds cases), editor (changes programs, pushes, publishes, deploys), approver (approves deploys), owner (its card, policy, members); its owner's or an admin's. remove: take it away.

## Models: which a program may use

Every provider lm15 reaches with an API key (OpenAI, Anthropic, Google Gemini, TypeSafe, xAI, OpenRouter, Groq, DeepSeek, …) can be used once a person adds its key (the **Models** page, or `quiver models key <provider>`, typed hidden: never pass a key on a command line, and never ask your person to paste one to you). Quiver asks the provider which models the key opens, and keeps each with its kind and price. `quiver models` (the `models` tool) lists them; `quiver models --group <group>` lists what that group's programs may be given, each allowed or not and why, named exactly as a program's `lm` names it: `gpt-6-luna`, `claude-haiku-5-5`, `jev-latest`, `openai:ft:gpt-4o-mini…` (a model whose name does not say its provider comes after it). TypeSafe's Jev answers judgments only (choices, yes or no, scores, with their probabilities; fast and cheap for classifying), never free text. A model without a known price (a fine-tuned one) is offered to no program until someone sets one (`quiver models price <model> --in X --out Y --note WHERE`); a group may use a provider only when its owner allows it (`quiver models allow <provider> <group>`).

## Give a program tools: use the shared ones

A program's model can run code, work with files and browse the web through **shared tools**, written once in `shared/tools/` (content) with what a tool needs done right: each task has its own workspace (a folder every tool of the task shares, thrown away after), code runs there with no secret and no network, stops after its time limit, and a PNG or JPEG it saves is shown to the model (so it can look at its own chart and fix it) and kept with the run (so people see it). Use them; do not write your own shell or file tool.

| tool | what the model does with it |
|---|---|
| `bash` | run shell commands in the workspace |
| `python` | run Python 3 (the machine's: standard library only on lambda) |
| `r` | run R (R 4.6 with ggplot2 on lambda); `ggsave("chart.png", p)` shows it the chart |
| `read_file`, `write_file`, `edit_file` | read a file (a picture is shown), write one, change an exact piece of one |
| `browse` | hand a task on the web to Browser Use's agent (costs up to $0.25 a call; it only looks, never signs in or buys) |

In code (`groups/<group>/<program>/program.ts`):

```ts
import { ai, t } from "functai";
import { r } from "../../../shared/tools/r.ts";
import { readFile } from "../../../shared/tools/read_file.ts";

export const build = () => ai("chart_maker", {
  description: "Make the chart the person asks for with ggplot2. Save it as chart.png, look at it, and fix it until it reads well.",
  input: { request: t.string() }, output: t.string(),
  tools: [r, readFile], maxSteps: 12, lm: "claude-haiku-5-5",
});
export default build();
```

Without code (`create`, or New program on a group's page): add `"tools": ["r", "read_file"]` (and `"max_steps": 12`) to the definition. A tool that reaches a service (`browse`) needs its group's `may_reach` to allow it. More time for code: `rWith({ seconds: 300 })` (also `bashWith`, `pythonWith`, `browseWith({ maxCostUsd })`).

The library, live: the `tools` MCP tool, `quiver tools`, `GET https://quiver.staging.bigspin.ai/api/tools`, the pages' **Tools**. **Add a tool** others can use (the editor role in `shared`): a file `shared/tools/<name>.ts` in a workspace (`quiver pull shared`, then `quiver push`) exporting a FunctAI `tool(name, { description, input, effects, needs?, codeId: codeIdOf(import.meta.url) }, run)`, its first comment saying what it is for (helpers it shares go in files starting with `_`, which list nothing); it is listed everywhere at once. Try any of them once, as a model would call it: `quiver tools r --try '{"code": "cat(1 + 1)"}'` (every trial is kept with its cost; a paid tool's counts toward your key's daily cap).

## Call an address (FunctAI's serving routes, contract/serving.md)

- `GET https://quiver.staging.bigspin.ai/a/<address>/interface`: its promise, what it takes and gives (`functai_interface` format 1); `GET …/openapi.json`: the same as OpenAPI 3.1
- `POST https://quiver.staging.bigspin.ai/a/<address>/call` `{"inputs": {…}}` → `{"call", "outputs", "value"}`
- `POST https://quiver.staging.bigspin.ai/a/<address>/stream` `{"inputs": {…}}` → Server-Sent Events

A conversation (FunctAI's routes, its shapes; each turn is shown the turns before it, so a program needs no history input; `<id>` is yours: a user's chat, `alex`; a key sees only its own):

- `POST https://quiver.staging.bigspin.ai/a/<address>/conversations/<id>/turns` `{"inputs": {…}, "after"?: "<turn>", "request_id"?: "…", "wait"?: true}` → 201 the turn: `{"turn", "parent", "state", "inputs", "outputs"?, "value"?, "conversation"}` (its id known before it runs; `wait`: once it ended). `after`: continue from that turn (a branch), else from the latest. The same `request_id` twice is one turn. One turn at a time: 409 `conversation-busy` while one runs.
- `GET https://quiver.staging.bigspin.ai/a/<address>/conversations/<id>/turns`: its turns (the latest turn's branch); `GET …/turns/<turn>`: one; `GET …/turns/<turn>/events`: Server-Sent Events as it is written; `POST …/turns/<turn>/stop`
- `GET https://quiver.staging.bigspin.ai/a/<address>/conversations`: your conversations here; `DELETE https://quiver.staging.bigspin.ai/a/<address>/conversations/<id>`: forget it, for good (its inputs, answers and records; that each turn ran and what it cost stay)

For work longer than a request can wait (Quiver's own routes):

- `POST https://quiver.staging.bigspin.ai/a/<address>/runs` `{"inputs": {…}, "notify"?: "https://…", "conversation"?: "<id>", "after"?: "<turn>"}` → 202, the run (`id`, `status`; with `conversation`, it is that conversation's next turn, and the run says its `turn`); send an `Idempotency-Key` so a retried start starts one run. Inputs that do not fit: 422 at once, `{"error": "inputs", "fields": {"<input>": ["what is wrong"]}}`
- `GET https://quiver.staging.bigspin.ai/a/<address>/runs/<id>`: where it stands (`running`, `done`, `failed`, `stopped`, `lost`), its answer once done, its cost
- `GET https://quiver.staging.bigspin.ai/a/<address>/runs/<id>/events`: Server-Sent Events, what it shows outside as it runs (its notes, its answer as written), kept: send `Last-Event-ID` to read on; ends with an `event: run` once it has ended
- `POST https://quiver.staging.bigspin.ai/a/<address>/runs/<id>/stop`: stop it (its calls cancelled, what it opened closed)
- `notify` (a key's runs): when it ends, the run is POSTed there, with `Quiver-Signature: t=<unix>,v1=<hex HMAC-SHA256 of "<t>.<body>" keyed with your key>`
- A key sees and stops only the runs it started.
- From FunctAI: `await remote("https://quiver.staging.bigspin.ai/a/<address>")` is the deployed program as a typed program.
- `/s/<address>/…` (its name until 2026-11-05) still answers, with `Deprecation: true` and a `Link` to `/a/`.

## Call a program or a version (the team's: a person, or an admin key)

The same routes, for one program alone or one exact version: `https://quiver.staging.bigspin.ai/p/<program>/…` (its latest version: `interface`, `call`, `stream`, `runs`, …) and `https://quiver.staging.bigspin.ai/b/<board>/…`. They change with every publish and keep no promise: a service never calls them. When a service needs a program, deploy it.

## The API (JSON; errors are `{"error", "detail"}`; writes take an `Idempotency-Key` header and `"preview": true`)

- `GET /api/programs`, `GET /api/programs/<name>`, `GET /api/addresses?group=`, `GET /api/addresses/<group>/<name>` (a former name leads to the address), `GET /api/groups`, `GET /api/groups/<name>`, `GET /api/find?q=` (words, and returns:T takes:x model:m group:g kind:k), `GET /api/boards?program=`, `GET /api/boards/<id>`, `GET /api/boards/<id>/files`, `GET /api/runs`, `GET /api/runs/<id>`, `GET /api/runs/<id>/events` (any run, live: Server-Sent Events, each piece of its answer with the answer read so far, `so_far`), `GET /api/runs/<id>/conversation` (the conversation it is a turn of: every turn, its branch, the runs that continue it), `GET /api/deploys/<id>`
- `POST /api/boards` `{program, files, examples?}` publish (examples: its examples.jsonl, taken in as cases) · `POST /api/boards/<id>/edit` `{program?, patch?}` · `POST /api/runs` `{program | address | board, inputs, continue?: <run id>}` (every run is a turn of a conversation: a new one, or with `continue` that run's next turn) · `DELETE /api/conversations/<id>` forget one · `POST /api/tests` `{program | address | board, split?}` · `POST /api/improve` `{program | address | board, budget?, minibatch?, teacher?, seed?, max_usd?, preview?}` (GEPA on its training cases; its choice a draft) · `POST /api/programs/<name>/promote` `{board}` (a draft made the latest) · `POST /api/deploys` `{address, program | board}` (a new address is made) · `POST /api/addresses/rename` `{from, to}` · `POST /api/deploys/<id>/approve` `{reason?}` (needed when it was measured worse) · `POST /api/deploys/<id>/withdraw` · `POST /api/programs` `{group, name, definition}` · `POST /api/groups` · `POST /api/groups/<name>/types` · `POST /api/runs/<id>/ratings` `{item, field?, verdict: yes | no | unsure | null}` or `{verdict: right | wrong | null, correction?}`, with `note?, blind?, sample?` · `POST /api/preferences` `{a, b (run:<id> | call:<id>, in the order shown), winner: a | b | tie | null, blind?}` · `GET /api/programs/<name>/measures` · `GET /api/tradeoffs?programs=a,b&steps=a` (every version's and step's answers: cost, time, case; scores by criterion with each case's value; core/pareto.ts places them) · `GET|POST /api/programs/<name>/cases` `{add | from_run | from_call | case, expected?, split?, retire?}` · `POST /api/programs/<name>/judge` `{draw? | runs?}` · `POST /api/runs/<id>/stop` · `GET /api/tools`, `GET /api/tools/<name>`, `POST /api/tools/<name>/try` `{input}` · `GET /api/keys`, `POST /api/keys` `{name, role?, daily_usd?}`, `DELETE /api/keys/<name>`

## More

- [Everything above, with the live catalogue: programs, where they are deployed, addresses and their promises, groups, versions](https://quiver.staging.bigspin.ai/llms-full.txt)
