[{"data":1,"prerenderedAt":2071},["ShallowReactive",2],{"docs-index":3,"docs-page-engine\u002Foverview":2048},[4,155,441,588,914,1100,1201,1256,1669],{"id":5,"title":6,"lead":7,"pages":8},"start","Get started","What VIABLE is made of, and its words.",[9,31,62,127],{"path":10,"title":11,"description":12,"updated":13,"minutes":14,"sections":15},"start\u002Fhow-it-fits","What's what in VIABLE","The VIABLE Learning Platform, the Developer Platform, the Engine and the Harness - what each is for, and which website is which.","2026-11-05",3,[16,19,23,27],{"id":17,"title":17,"text":18},"","**VIABLE** is a family of tools for AI that teaches the way a teacher wants, made by [VIABLE Lab](https:\u002F\u002Fwww.viablelab.org). It has four parts, and you need only the one that fits what you want to do. You want to Use It is **Teach a class today**, with nothing to set up [VIABLE Learning Platform](https:\u002F\u002Fwww.viablelearning.org) Hosted. Say what you're teaching, and share a link **Run a study, build agents, or call an API** [VIABLE Developer Platform](https:\u002F\u002Fdevelopers.viablelab.org) Hosted. Every setting, with nothing to run **Run it all on your own server** [VIABLE Engine](\u002Fdocs\u002Fengine\u002Fover",{"id":20,"title":21,"text":22},"the-names","The names","**VIABLE** on its own means the whole family, never one part. It comes from the lab's name: Versatile Innovations in Affect, Behavior, and Learning Engineering. **VIABLE Lab** is the people, at the University of Florida. It's where to [ask for more credit, or anything else](https:\u002F\u002Fwww.viablelab.org\u002Fcontact). **VIABLE Learning Platform** (VLP) is where teachers and learners go. **VIABLE Developer Platform** (VDP) is where researchers and builders work. These docs live here, and cover all four parts. **The VIABLE API** is how programs reach the Developer Platform. It's part of it, not a fifth t",{"id":24,"title":25,"text":26},"how-they-stand-on-each-other","How they stand on each other","Each stands on the one below it, one way only, so nothing is built twice: **The same pages:** what a teacher sees on the Learning Platform is the Engine's console. A learner's page is the same on all three. **The same courses:** the Learning Platform and the Developer Platform are one account and one set of courses, under your Google sign-in. A course made in one shows in the other. **A way out:** a course or agent on the Developer Platform downloads as a package that runs on the Engine, and the other way round. Learners and their work don't travel in one.",{"id":28,"title":29,"text":30},"which-website-is-which","Which website is which","`viablelearning.org` is for teaching and learning. `viablelab.org` is for the lab and for building. Address What it is For [www.viablelearning.org](https:\u002F\u002Fwww.viablelearning.org) The Learning Platform's front door, and where learners type a class code Teachers and learners [teach.viablelearning.org](https:\u002F\u002Fteach.viablelearning.org) Where a teacher sets up a class Teachers page.viablelearning.org What learners open from a teacher's link. Nothing to sign up for Learners [developers.viablelab.org](https:\u002F\u002Fdevelopers.viablelab.org) The Developer Platform, and these docs Researchers, builders, de",{"path":32,"title":33,"description":34,"updated":13,"minutes":35,"sections":36},"start\u002Fkey-ideas","Key ideas","Agents, activities, studies and evidence - the four ideas the Developer Platform is built on, and who does what.",2,[37,39,42,46,50,54,58],{"id":17,"title":17,"text":38},"VIABLE sits between AI models and learners. It turns what a teacher or researcher intends into what the AI does with each learner, and keeps the evidence, for K-12 classes, university courses, adult learning, and libraries, museums and websites. Not sure which part of VIABLE you need? [What's what in VIABLE](\u002Fdocs\u002Fstart\u002Fhow-it-fits).",{"id":40,"title":41,"text":17},"four-ideas","Four ideas",{"id":43,"title":44,"text":45},"1-agent-how-the-ai-works","1. Agent: how the AI works","An **agent** is an AI made of steps: it can search a knowledge base, check its reply with a **VETTING** step, then answer. Each publish is a numbered version, and learners always meet the published one. [Your first agent](\u002Fdocs\u002Fbuild\u002Ffirst-agent)",{"id":47,"title":48,"text":49},"2-activity-what-learners-do","2. Activity: what learners do","A **course** is one link a class opens all term. Each **activity** in it puts the work, such as **Writing** or **Questions**, beside the AI, and **How the AI helps** says what it is for and how far the AI may go. A library can put an agent on a **chat page** instead, with no sign-in. [Your first class](\u002Fdocs\u002Fteach\u002Ffirst-class)",{"id":51,"title":52,"text":53},"3-study-what-a-study-decides","3. Study: what a study decides","A **study** sits over courses or a chat page. It asks for consent, decides who gets which version, and pins each agent's version while it runs. [Your first study](\u002Fdocs\u002Fresearch\u002Ffirst-study)",{"id":55,"title":56,"text":57},"4-evidence-what-is-recorded","4. Evidence: what is recorded","Every **run** keeps each step and VETTING round. Learners' messages and the replies are **turns**, kept beside their **work**. Researchers code them and download one documented export, by learner code. [The research export](\u002Fdocs\u002Fresearch\u002Fexport)",{"id":59,"title":60,"text":61},"who-does-what","Who does what","Who What they do Where they work Teacher, librarian, workshop lead Runs classes, follows progress **Teach**: Courses, Question sets, Learners Researcher Runs studies and their data **Research**: Studies, Coding, Export Builder, learning designer Makes and tests agents **Build**: Agents, Knowledge App developer Connects an app **Build → Apps**, and the API Owner Members, keys and costs **Operate**, **Account** Learners need no account: they open one link. These are jobs, not permissions. A member's project role decides what they can change, and a course can be shared with one teacher. See [Memb",{"path":63,"title":64,"description":65,"updated":66,"minutes":67,"sections":68},"start\u002Fglossary","Glossary","Look up the words VIABLE uses on screen and in these docs, each in one line, with a link to where it is explained.","2026-11-02",4,[69,71,75,79,83,87,91,95,99,103,107,111,115,119,123],{"id":17,"title":17,"text":70},"The words VIABLE uses on screen and in these docs, one line each. Use it when a term is new to you.",{"id":72,"title":73,"text":74},"a","A","**Activity**: One thing to do in a course: a [lesson, pre-test, post-test or survey](\u002Fdocs\u002Fteach\u002Factivities). **Agent**: An [AI workflow made of steps](\u002Fdocs\u002Fbuild\u002Ffirst-agent). Learners and apps meet its published version. **Agent test**: A [case](\u002Fdocs\u002Fbuild\u002Fagent-tests) (what a learner says, what a good reply does) run against a version of an agent, its reply rated pass or fail by a judge model. **Agent profile**: An agent's name, avatar, greeting, suggested questions and notice, shown on its [chat page](\u002Fdocs\u002Fbuild\u002Fchat-pages) and in activities. **AI key**: The [API key](\u002Fdocs\u002Fproject\u002Fkeys",{"id":76,"title":77,"text":78},"b","B","**Block**: A [group of steps](\u002Fdocs\u002Fbuild\u002Fsteps#groups-and-blocks) folded into one box on the canvas. It can say what the steps do and show some of their settings.",{"id":80,"title":81,"text":82},"c","C","**Chat page**: An agent published for [open use on websites](\u002Fdocs\u002Fbuild\u002Fchat-pages). Visitors need no account. **Codebook**: [Codes](\u002Fdocs\u002Fresearch\u002Fcoding) with definitions and examples, for coding messages, turns or AI replies. **Comparative study**: A study that sets what learners get, [comparing groups or units](\u002Fdocs\u002Fresearch\u002Fdesigns). Courses only. **Condition**: What one group gets in a comparative study, such as [no AI or another agent](\u002Fdocs\u002Fresearch\u002Fconditions). **Consent**: A learner's decision on a study's [versioned form](\u002Fdocs\u002Fresearch\u002Fconsent). It decides whose data counts. **Co",{"id":84,"title":85,"text":86},"f","F","**Durable skill**: Collaboration, communication or critical thinking at one of four levels, from the Carnegie Skills Progressions, for an [activity to aim at](\u002Fdocs\u002Fteach\u002Fstandards-and-skills). **Final codes**: The codes the team [decides](\u002Fdocs\u002Fresearch\u002Fcoding) where coders differ. **For each**: A step that runs [the same steps once for every item](\u002Fdocs\u002Fbuild\u002Fsteps#for-each) of a list, then carries on with what each pass kept. **Flag**: A moment a teacher should see, such as a safety reply, under **To look at** in [Progress](\u002Fdocs\u002Fteach\u002Fprogress).",{"id":88,"title":89,"text":90},"h","H","**How the AI helps**: An activity's [goal, standard and how far the AI may go](\u002Fdocs\u002Fteach\u002Fai-helps).",{"id":92,"title":93,"text":94},"l","L","**Learner**: A person known to the project by a learner code, the same on every device. **Learner code**: A learner's ID in data and exports, like `L-7F3KQ2`. Names never leave VIABLE. **Learning component**: A smaller piece a [standard](\u002Fdocs\u002Fteach\u002Fstandards-and-skills) breaks into. **List**: An LLM step's field that holds [several items](\u002Fdocs\u002Fbuild\u002Fsteps#llm), each with fields of its own, such as pictures to draw.",{"id":96,"title":97,"text":98},"m","M","**Member**: A person in a project, with a [role](\u002Fdocs\u002Fproject\u002Fmembers-and-roles). **Misconception**: A common mistake that hinders a math skill, from Eedi's graph, that an activity's AI can [listen for](\u002Fdocs\u002Fteach\u002Fstandards-and-skills). **Model coder**: A model that [codes a sample](\u002Fdocs\u002Fresearch\u002Fcoding) as one more coder, with a confidence and a reason per item.",{"id":100,"title":101,"text":102},"o","O","**Observational study**: A study that [follows how](\u002Fdocs\u002Fresearch\u002Fdesigns) courses or a chat page are used, with no conditions.",{"id":104,"title":105,"text":106},"p","P","**Panel**: What learners work in: [Questions, Writing, Reading or Embedded page](\u002Fdocs\u002Fteach\u002Factivities). **Personal code**: A learner's [code](\u002Fdocs\u002Fteach\u002Fsign-in) on a printed card, like `K7Q2-M9XC`. A personal link skips typing it. **Picture number**: How a learner refers to a picture a conversation showed (\"picture 2\"); later turns [change it by that number](\u002Fdocs\u002Fbuild\u002Fsteps#image). **Project**: A team's space for agents, courses, studies, keys and logs, paid for by its [owner](\u002Fdocs\u002Fproject\u002Fmembers-and-roles). **Provider key**: Your own model provider's key, billed there instead of [VIAB",{"id":108,"title":109,"text":110},"r","R","**Research notice**: A study's text people read [before taking part](\u002Fdocs\u002Fresearch\u002Fconsent). Not the agent's notice. **Role**: Owner, editor or viewer: what a [member](\u002Fdocs\u002Fproject\u002Fmembers-and-roles) can change. Viewers see codes, not names. **Run**: One execution of an agent, step by step, listed in its [Run history](\u002Fdocs\u002Fbuild\u002Ftesting). **Run step**: Runs [one step once](\u002Fdocs\u002Fbuild\u002Ftesting#run-one-step) from the last Try it run, to try a change to it. Nothing is saved.",{"id":112,"title":113,"text":114},"s","S","**Section**: A class period or group, with [its own link](\u002Fdocs\u002Fteach\u002Fsign-in) and opening times. **Section link**: A course link for one section, like `…?section=p2`. **Standard**: A learning standard an [activity aims at](\u002Fdocs\u002Fteach\u002Fstandards-and-skills), kept as it was when chosen. **Step settings**: The panel where a step is [set up](\u002Fdocs\u002Fbuild\u002Fsteps#how-a-step-s-settings-are-laid-out): Setup, Options, When something goes wrong and what later steps can use. **Study**: A [protocol](\u002Fdocs\u002Fresearch\u002Ffirst-study) over courses or a chat page: consent, design and data.",{"id":116,"title":117,"text":118},"t","T","**Trace**: A [run's record](\u002Fdocs\u002Fconnect\u002Flearners-and-traces) of what each step received and returned, and each VETTING round. **Turn**: A learner's message and the reply, kept in the [export](\u002Fdocs\u002Fresearch\u002Fexport).",{"id":120,"title":121,"text":122},"u","U","**Unit**: A week or topic grouping a course's [activities](\u002Fdocs\u002Fteach\u002Factivities), with optional dates.",{"id":124,"title":125,"text":126},"v","V","**Values**: A step that [works out named values](\u002Fdocs\u002Fbuild\u002Fsteps#values) from earlier steps, each able to use the ones above it. **VETTING**: A step where a [reviewer model](\u002Fdocs\u002Fbuild\u002Fvetting) checks each reply against your rules and sends failed ones back to be rewritten. **Visitor**: An anonymous chat page user: one browser on one device, shown in data by a visitor code (`V-…`). [Key ideas](\u002Fdocs\u002Fstart\u002Fkey-ideas): what VIABLE is made of, and who does what. [Your first class](\u002Fdocs\u002Fteach\u002Ffirst-class): make a course and share its link.",{"path":128,"title":129,"description":130,"updated":131,"minutes":14,"sections":132},"start\u002Fopen-source","Open source","The VIABLE Harness makes any AI agent teach the way a teacher wants. The VIABLE Engine runs agents, courses and class lists on your own server.","2026-11-04",[133,135,139,143,147,151],{"id":17,"title":17,"text":134},"Two parts of VIABLE are open, under the MIT license, in [one repository](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable). The code that runs every agent here is the same code, not a second product. You have You want Take An AI agent or an education product It to know how to teach, whatever it's built on The Harness A school, a library or a lab, and your own server Agents, courses and class lists you run yourself, on any model The Engine A course or agent here The same thing on your own server The Engine, from a package A study, a workflow to draw, nothing to host Consent, a research export, a v",{"id":136,"title":137,"text":138},"the-harness","The Harness","A model on its own replies generally: it gives the answer, explains too much, and answers everyone alike. The Harness works out what this moment needs from what the teacher set and open resources such as standards and misconceptions, and gives it to the model before it replies. Afterward it can check the reply and record what happened. **Your AI assistant, with nothing to install.** `npx skills add VIABLE-Learning-Lab\u002Fviable --skill teaching-with-viable` teaches it the moves. [VIABLE's hosted MCP server](\u002Fdocs\u002Fconnect\u002Fassistants) gives it the tools. **Any app, by changing one URL.** The gatewa",{"id":140,"title":141,"text":142},"the-engine","The Engine","Your own server, set up in the browser: `npx github:VIABLE-Learning-Lab\u002Fviable start`, and its console opens. **Add a model.** Any OpenAI-compatible provider, on your own machine or elsewhere. Say what each model is for, and voice, reading aloud, attachments and search by meaning turn on as you do. **Make a course, or add one.** Activities, how the AI helps in each, and when each is open. **Say who is in it.** Anyone with the link, or a class list with personal codes and sections. **Share it, and follow it.** A link or QR code, then Progress. It also makes chat pages for a website, and agents ",{"id":144,"title":145,"text":146},"move-a-course-or-agent-to-your-own-server","Move a course or agent to your own server","**Download it.** On a course, press **Download as package → With its files (.zip)**. For an agent with a chat page and no course, open **Agents**, then **⋯ → Export… → With its knowledge (.zip)**. **Add it.** In the Engine's console: **Courses → From a file**. **Choose the models.** On **Models**. An agent's steps use the models you set there. **Tell learners.** It is a new link. Learners and their work stay here, so paste your class list there and share the new link. It goes the other way too: download it from the course's **Settings** in the console, then import it here from **Courses**. Mod",{"id":148,"title":149,"text":150},"what-stays-on-viable","What stays on VIABLE","Here On the Engine The visual workflow editor, Try it and the Assistant A form for simple agents. Workflows from here run as they are Knowledge bases from any file and from websites, kept up to date Text, Markdown, CSV, JSON and HTML files, and snapshots of websites you refresh Consent, studies, coding and the research export Who has done what, how replies went, suggested changes, and your own records Logs, usage, keys and billing Your model provider's bill",{"id":152,"title":153,"text":154},"credit","Credit","Both carry open data from [Learning Commons](\u002Fdocs\u002Fresources\u002Flearning-commons) under CC BY 4.0, with the attribution to keep. If you use them in research, cite them as the repository says. [Examples and packages](\u002Fdocs\u002Fteach\u002Fexamples-and-packages): download any course as a package. [Your first run](\u002Fdocs\u002Fconnect\u002Ffirst-run): the request your app sends. [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting): the reply check the Harness also runs.",{"id":156,"title":157,"lead":158,"pages":159},"teach","Teach","Run a class, a workshop or a seminar with an AI beside the work.",[160,187,219,247,282,301,332,355,383,410],{"path":161,"title":162,"description":163,"updated":13,"minutes":14,"sections":164},"teach\u002Flearning-platform","Teach on the VIABLE Learning Platform","Sign in with Google, say what you're teaching, and get a course with a link for your class, with no agents or keys to set up.",[165,167,171,175,179,183],{"id":17,"title":17,"text":166},"The simplest way to teach with VIABLE. [teach.viablelearning.org](https:\u002F\u002Fteach.viablelearning.org) asks for a Google account and nothing else: no agents, keys or models to choose. Use it when you want a class running today. The rest of this section describes the same courses on the VIABLE Developer Platform, which has more settings. The words on the pages are the same, and a course made in one shows in the other under the same Google account.",{"id":168,"title":169,"text":170},"set-up-a-class","Set up a class","**Sign in.** Open [teach.viablelearning.org](https:\u002F\u002Fteach.viablelearning.org) and continue with Google. **Say what you're teaching.** The subject, who it's for, and what they find hard. Ask makes the course and its activities and shows you each step. If you'd rather fill in forms, **Courses** has them, and the two work on the same course. **Try it as a learner.** **Try it** opens the course as your learners will see it. What you do there stays out of Progress. **Add your class, if you want names.** Paste a list on **People**. Ask gives you a box for it: what you paste there goes straight to t",{"id":172,"title":173,"text":174},"what-it-costs","What it costs","What Who pays What happens at the end Setting up with Ask The platform, within a daily limit Ask says so and comes back the next day. Everything else keeps working Your learners' conversations with the AI Your AI credit Learners can still open their activities, write and answer questions. The AI beside them stops replying, and tells them to let you know The credit left is on every page. A class of 30 sending 10 messages each uses roughly $0.10 to $0.20. **AI credit** has a button for asking VIABLE Lab for more.",{"id":176,"title":177,"text":178},"how-your-learners-sign-in","How your learners sign in","Choose on a course's **People** page. Way What it means Good for **Personal codes on cards** You print a card for each learner, and only codes on your list open the course Children: nothing to spell, and their work follows the code on any computer **They type their name or student ID** The first time, on one device. Anywhere else they use the personal code they are shown then A class that knows its own names **Nobody signs in** Each browser is one learner One sitting. On another computer, they are someone new",{"id":180,"title":181,"text":182},"the-two-tutors","The two tutors","Each activity with AI uses one of two, and its page says which. **Tutor.** While a question is open, each reply is checked against the answer key before the learner sees it. Replies take about ten seconds. **Quick tutor.** No check, so replies come in a few seconds, and one may give an answer away. Use it where there is no answer to give away.",{"id":184,"title":185,"text":186},"when-you-d-want-the-developer-platform","When you'd want the Developer Platform","For agents of your own, knowledge bases, other models, a project with colleagues, and studies with consent and conditions. Sign in at [developers.viablelab.org](https:\u002F\u002Fdevelopers.viablelab.org) with the same Google account, and your courses are there, in your personal project. [What each part of VIABLE is for](\u002Fdocs\u002Fstart\u002Fkey-ideas).",{"path":188,"title":189,"description":190,"updated":191,"minutes":14,"sections":192},"teach\u002Ffirst-class","Your first class","Make a course, add one activity with an AI, share its link or QR code, and see your first learner in Progress.","2026-10-30",[193,195,199,203,207,211,215],{"id":17,"title":17,"text":194},"This page takes you from nothing to a working class in about ten minutes. Use it the first time you teach with VIABLE.",{"id":196,"title":197,"text":198},"before-you-start","Before you start","You need a project where you are an **owner** or **editor**, and a **published agent** in it: the AI your learners talk with. If you have none yet, start with [Your first agent](\u002Fdocs\u002Fbuild\u002Ffirst-agent).",{"id":200,"title":201,"text":202},"make-the-course","Make the course","**Start a course.** Open [Teach → Courses](\u002Fdashboard\u002Fcourses) and press **New course**. **Name it.** Type a **Name**, such as \"Biology 101 · Period 2 and 3\". **Choose what kind.** Under **What kind of course**, pick **A class over weeks**, **A one-off session** or **A course for research**. **Say who it is for.** Choose **Adults**, **Teenagers** (13 to 17) or **Children** (under 13). **Create it.** Press **Create course**. It starts as a **Draft**, so its link doesn't open for learners yet. **Who it is for** decides how learners get in. Children always get personal codes on printed cards. In ",{"id":204,"title":205,"text":206},"add-an-activity-with-the-ai","Add an activity with the AI","**Pick a kind.** On the **Activities** tab, choose **Writing task**. **Check the AI.** **Learners talk with** is already set to your most recently changed published agent. Pick another if you like. **Write the task.** Under **Content**, type **The task**, for example \"Should school start later? Take a side and give two reasons.\" **Add it.** Leave **Opens** on **Open** and press **Add activity**. A preview appears beside the form: \"As a learner sees it · nothing here counts\". It shows the Writing panel with your agent beside it, on **Desktop** or **Phone**.",{"id":208,"title":209,"text":210},"choose-how-it-looks","Choose how it looks","In the course's **Settings → Appearance**, pick a look for its home and activities, and for the study's consent step if it has one. **Clear** is the default; **Playground** suits young learners. Under **Color**, keep the look's own or choose another; text on it turns dark or light so it stays readable. Press **Preview** to see it at full size, on a computer or a phone, in light and dark.",{"id":212,"title":213,"text":214},"open-and-share-it","Open and share it","**Clear what's missing.** **Before it opens** lists anything left. If it asks you to **Choose the AI key that pays for its AI**, pick one in **Settings → The AI** and press **Save settings**. **Try it.** Press **Preview** to go through the course as a new learner. Beside an activity with an AI, **What the AI did** shows what happened behind each reply. Previews stay out of Progress. The link works for anyone who has it for 4 hours, so keep it from learners. **Open it.** Press **Open course**. **Share it.** On the **Link & QR** tab, copy **The course's link**, or press **Show to the class** for",{"id":216,"title":217,"text":218},"see-your-first-learner","See your first learner","A learner opens the link, types their name and starts. On the **Progress** tab, they appear with a dot under the activity once they start, and a check once they're done. That's a working class. [Activities and panels](\u002Fdocs\u002Fteach\u002Factivities): lessons, tests, surveys, and the panels learners work in. [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps): tell the AI the goal and how far it may go. [Learners and sign-in](\u002Fdocs\u002Fteach\u002Fsign-in): sections, your class list, code cards and shared computers.",{"path":220,"title":221,"description":222,"updated":223,"minutes":35,"sections":224},"teach\u002Factivities","Activities and panels","Choose an activity's kind, give learners panels to work in, show them in steps, and set what the AI sees.","2026-10-29",[225,227,231,235,239,243],{"id":17,"title":17,"text":226},"An activity is one thing learners do in a course, with an AI beside it or none. Use this page when you add a lesson, a test or a survey.",{"id":228,"title":229,"text":230},"kinds-of-activity","Kinds of activity","On the course's **Activities** tab, press **Add an activity** and choose a kind: **Lesson**: panels, a chat, or both. Start from **Chat with an AI**, **Practice questions**, **Writing task**, **Reading** or **Embedded page**. **Pre-test** and **Post-test**: no AI, one try, no hints, results at the end or none. A pre-test stays open to learners who join after it closes. **Survey**: your form's **Survey address**. The form receives the learner's ID or learner code under **The ID goes in** (`pid` by default).",{"id":232,"title":233,"text":234},"panels","Panels","Panels are what learners work in. An activity has up to six; with none, it is a chat. **Questions**: a **Question set**, with your choice of feedback, tries and hints. **Writing**: **The task**, an optional plan, a **Rubric** of up to 12 criteria, word limits and **A Hand in button**. Drafts save as learners write. **Reading**: a PDF from your knowledge bases, or **Upload a PDF**. Learners can highlight and add notes. **Embedded page**: **The page's address**, such as a simulation. It must allow `https:\u002F\u002Fpage.viablelearning.org` to frame it.",{"id":236,"title":237,"text":238},"showing-panels-in-steps","Showing panels in steps","With two or more panels, set **How the panels are shown** to **In steps** (up to four). Two ready patterns: **Read, then answer** and **Read with the AI, then answer alone**. For each step, choose what it **Shows**, whether **The AI** is **There** or **Not in this step**, and how it **Opens**: **When they're ready** or **On its own** after some minutes. A later step's questions don't reach learners' browsers early. A step without the AI is the sure way to keep it out of part of an activity, such as a final quiz.",{"id":240,"title":241,"text":242},"what-the-ai-sees","What the AI sees","Each panel has **What [agent] is shown of this panel**: Panel Choices Writing **All**, **Task only**, **Nothing**, **Custom** Questions **The question**, **Nothing**, **Custom** Reading **The page in view**, **The selection**, **The whole document**, **Nothing**, **Custom** Embedded page **Its events**, **Nothing**, **Custom** **Learners are told** shows what learners read about it. **Preview what [agent] reads** shows exactly what the AI gets. Answer keys are never in it.",{"id":244,"title":245,"text":246},"voice-and-photos","Voice and photos","Learners can speak if the agent's published version has a Transcribe step, and hear replies if it has a Speech step. Then turn on **Learners may speak** and choose **Read replies aloud**. Transcription is billed per audio minute. **Learners may attach photos** and **Learners may attach PDFs** allow up to 4 files a message, 8 MB each. If they aren't offered, the editor names the agent step to change. Every saved change makes a new version, which learners get the next time they open the activity. [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps): tell the AI the goal and how far it may go. [Question sets",{"path":248,"title":249,"description":250,"updated":131,"minutes":67,"sections":251},"teach\u002Fai-helps","How the AI helps","Tell the AI what an activity is for in your own words, choose how far it may go, and give a section its own support.",[252,254,258,262,266,270,274,278],{"id":17,"title":17,"text":253},"**How the AI helps** tells the agent what one activity is for and how far it may go. It appears in the activity editor once you choose who **Learners talk with**. The agent reads it with every message. Learners don't see it.",{"id":255,"title":256,"text":257},"goal","Goal","Under **Goal**, write what learners should be able to do by the end, in your own words. For example: \"Explain each step of a two-step word problem in their own words.\" A goal alone is enough for any learners, from a university seminar to a library session. You don't need a standard.",{"id":259,"title":260,"text":261},"a-standard-a-skill-or-misconceptions","A standard, a skill or misconceptions","**Standard or skill** is optional: a K-12 standard, or a durable skill for any age. For math, **Misconceptions to listen for** suggests common misconceptions. See [Standards, skills and misconceptions](\u002Fdocs\u002Fteach\u002Fstandards-and-skills). An activity can also hold **guidance from research**: short pieces from a framework or a study, each with where it came from. They come with a course package or through the API, and How the AI helps lists them. The AI reads them where they fit.",{"id":263,"title":264,"text":265},"how-far-the-ai-may-go","How far the AI may go","Choose one, or leave all four off: **Ask first**: it asks what they think or have tried, then builds on it. **Hints only**: hints and guiding questions, a step at a time, never the answer. **Explain when stuck**: it explains and shows a worked example, then they try one. **Only check**: it says what is right and what to look at again in their own work.",{"id":267,"title":268,"text":269},"only-what-each-moment-needs","Only what each moment needs","Turn on **Give [agent] only what each moment needs** and it stops getting everything you set with every message. It reads what the learner just did (gave an answer, asked for the answer, got stuck, or tried more than once), then gets your goal and how far it may go, a teaching move for that moment, and only the parts of the standard and misconceptions that message calls for. It is read by simple rules, so it adds no cost and no wait. The rules read English best. In a small first check it mostly helped replies hold back the answer, and that check isn't a study. **Preview** the activity and **Wh",{"id":271,"title":272,"text":273},"guidance-from-research","Guidance from research","Under **Guidance from research**, choose what you want the AI to have in mind: sentences on scaffolding, feedback, tutoring talk, self-explanation, worked examples, productive struggle, recall and spacing, and metacognition. Each names the studies it draws on, and each topic says what the research shows and what isn't known. Most of it is about human tutors, so try the activity and see what it does. You can choose up to 20. With each message the AI gets at most two that fit what the learner just did, and they take turns as the conversation goes on. With **Only what each moment needs** off, it ",{"id":275,"title":276,"text":277},"different-for-a-section","Different for a section","Under **Different for a section**, give a section, such as an extra-support period, its own **How far** choice and a note (up to 500 characters) the agent reads only in that section's chats. Each message's record keeps which section's settings it used (`teaching_section` in the [research export](\u002Fdocs\u002Fresearch\u002Fexport)). It isn't offered on a study's design or while a course's study runs, where every section follows the design, nor for one learner: a note about a person would go to a model.",{"id":279,"title":280,"text":281},"check-what-the-ai-is-given","Check what the AI is given","Open **What [agent] reads with every message** to see the exact text the agent gets. With a section of its own, choose it under **As read by**. This is an instruction, not a lock. To keep the AI out of part of an activity, show the panels in steps with a step that has no AI. See [Activities and panels](\u002Fdocs\u002Fteach\u002Factivities). With a VETTING step, its reviewer checks each reply against this too, and replies that can't pass show up under **To look at**. Progress also suggests changes here from what the activity's records show, such as less help when replies keep giving too much away. **Edit fir",{"path":283,"title":284,"description":285,"updated":191,"minutes":35,"sections":286},"teach\u002Fstandards-and-skills","Standards, skills and misconceptions","Align an activity to a K-12 standard or a durable skill, and give its AI common math misconceptions to listen for.",[287,289,293,297],{"id":17,"title":17,"text":288},"In an activity's [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps), you can say what it aims at beyond your own goal: a K-12 standard or a durable skill, and for math, the misconceptions its AI should listen for. All of it is optional and comes from [Learning Commons](\u002Fdocs\u002Fresources\u002Flearning-commons). Under **Standard or skill**, choose **K-12 standard** for US state standards and Common Core, or **Durable skill** for collaboration, communication and critical thinking at any age.",{"id":290,"title":291,"text":292},"a-k-12-standard","A K-12 standard","Standards cover math, English language arts, science and social studies. **Say where.** Choose your **State** (or **Common Core (Multi-State)**), a **Subject** and a **Grade**. **Search.** Type words, or a code such as `4.OA.A.3`, and press **Search**. **Pick one.** It comes with what **It breaks into** and the **Standards before it**, for the AI to go back to. **Untick what doesn't fit.** Untick what this activity doesn't reach.",{"id":294,"title":295,"text":296},"a-durable-skill","A durable skill","Durable skills come from the Carnegie Skills Progressions. Each indicator, such as \"Check in and circle back with group progress\", grows through four levels: **Exploring**, **Analyzing**, **Integrating** and **Extending**. Choose the **Level learners work toward**, then search with words like *evidence* or *feedback*. The AI reads what a learner does **At this level**, and **The level before** to go back to. Either way, what you choose is kept in the activity as it was, with its CC BY 4.0 attribution. Later changes at Learning Commons don't change what your learners' AI was given.",{"id":298,"title":299,"text":300},"misconceptions-to-listen-for","Misconceptions to listen for","For math, **Misconceptions to listen for** suggests misconceptions from Eedi's graph, through [Learning Commons](\u002Fdocs\u002Fresources\u002Flearning-commons): at once for a math standard, or, with only a goal, from **Teaching math? Find misconceptions to listen for**. Each suggestion is a math skill whose words match the standard or goal, with the misconceptions that hinder it; you can also search with your own words. Tick up to eight; nothing is ticked for you. The agent, and a VETTING reviewer, get them as a short list. [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps): the goal, how far the AI may go, and a se",{"path":302,"title":303,"description":304,"updated":223,"minutes":14,"sections":305},"teach\u002Fsign-in","Learners and sign-in","Choose how learners sign in, set up sections and a class list, print code cards, and give a section its own times.",[306,308,312,316,320,324,328],{"id":17,"title":17,"text":307},"Set up who is in a course and how they get in, before your first class or a new term.",{"id":309,"title":310,"text":311},"how-learners-sign-in","How learners sign in","Choose in **Settings → How learners sign in**. It's the same in every activity, so a learner's work follows them across devices. Option Best for **Personal codes on cards** Young learners and shared devices **They type their name** Quick starts with no list **They type an ID they already have** Matching your records **Their ID comes in the link** Arrivals from a survey or online panel **Each device** One sitting: each browser is one learner A typed name opens someone's work only the first time, or on the browser they used. Anywhere else the page asks for their code, which their course home sho",{"id":313,"title":314,"text":315},"sections","Sections","A section is a class period or group, with its own link. On **People**, type a name under **Sections**, press **Add section**, then **Save sections**. Section links appear on **Link & QR**. Then choose **How learners get into their section**: **By their section's link**, **They pick it themselves, once**, or **From your class list only**.",{"id":317,"title":318,"text":319},"your-class-list","Your class list","A class list is optional. It gives everyone a section and a personal code at once, and shows them in Progress before their first visit. **Open the list.** On **People**, press **Add people**. **Paste your names.** Type one person per row, or paste rows from a spreadsheet. **Import a CSV file** works too. **Fill the gaps.** **Student ID or email** and **Section** are optional; **Section for rows that leave it empty** fills them. **Add them.** Check the summary and press **Add**. Add up to 1,000 people at a time. Anyone already in the course keeps their work and code.",{"id":321,"title":322,"text":323},"code-cards","Code cards","Everyone on the list gets a **personal code**, like `K7Q2-M9XC`. **Print cards** on **People** prints each learner's name, section, code and a QR code for their section's link. Allow pop-ups if they don't open. For a lost card, press **New code** on the learner's page in [Teach → Learners](\u002Fdashboard\u002Flearners).",{"id":325,"title":326,"text":327},"shared-computers","Shared computers","For a laptop cart or a lab, turn on **Shared computers** in **Settings → How learners sign in** (on by default for children). Each visit, and any return after 20 minutes idle, asks \"Are you Sam?\". Learners also get **Sign out**. Don't use **Each device** on shared computers: everyone on that browser becomes one learner.",{"id":329,"title":330,"text":331},"opening-times-and-extra-time","Opening times and extra time","An activity's **Opens** is **Open**, **At a time** or **When I open it**; **Closes** is optional. Once it's saved, each section gets its own row, so Period 2 can start Monday and Period 3 Tuesday. In class, **Open now** asks **Everyone** or one section **only**. For extra time, open the learner's cell in **Progress**, set **Open for them until** under **More time**, and press **Give more time**. It only moves a close later. [Following progress](\u002Fdocs\u002Fteach\u002Fprogress): see who has done what, and what to look at. [Sharing a course](\u002Fdocs\u002Fteach\u002Fsharing): run it with other teachers and assistants.",{"path":333,"title":334,"description":335,"updated":223,"minutes":35,"sections":336},"teach\u002Fsharing","Sharing a course","Share one course by email with a teacher who runs it or an assistant who follows it, without giving them the rest of the project.",[337,339,343,347,351],{"id":17,"title":17,"text":338},"Share one course with a co-teacher or an assistant, like sharing a document. They get that course, not the rest of your project.",{"id":340,"title":341,"text":342},"share-it","Share it","**Open the course.** In **Teach → Courses**, open it and press **Share** at the top. **Add them.** Type their email, choose **Teacher** or **Assistant**, and press **Add**. **That's it.** We email them the link. They have the course as soon as they sign in with that email, with or without an account today. There is nothing to accept. Under **People with access**, the dialog lists everyone in the project first, then each person you shared it with.",{"id":344,"title":345,"text":346},"who-can-do-what","Who can do what","Teacher and assistant are roles on this course; editor and viewer are roles in the whole project. Teacher Assistant Editor Viewer Progress, flags and learners' work, with names Yes Yes Yes Codes only Mark what to look at as seen Yes Yes Yes No Class list, sections, opening times, extra time Yes No Yes No The course's settings (not its AI key) Yes No Yes No Share the course Yes No Yes No Activities, the AI key, deleting the course No No Yes No A teacher or assistant from outside the project sees only the courses shared with them: the project switcher shows the project as **Shared course**, and ",{"id":348,"title":349,"text":350},"a-study-in-several-schools","A study in several schools","A study's courses can sit in one project, one per school. Share each school's course with its own teachers: each sees only their course and its learners. The activities stay the study's design, the same at every school. Project editors can change every course in the project. Make someone an editor only if they build activities or the study; share a course with a teacher who runs one class.",{"id":352,"title":353,"text":354},"stop-sharing","Stop sharing","In **Share**, choose another role from a person's role menu, or press **×** to remove them. They lose the course at once; what their learners did stays. A teacher can remove themselves, which the dialog calls leaving the course. [Learners and sign-in](\u002Fdocs\u002Fteach\u002Fsign-in): sections, code cards and extra time. [Following progress](\u002Fdocs\u002Fteach\u002Fprogress): see who has done what, and what to look at. [Members and roles](\u002Fdocs\u002Fproject\u002Fmembers-and-roles): roles that cover the whole project.",{"path":356,"title":357,"description":358,"updated":359,"minutes":67,"sections":360},"teach\u002Fprogress","Following progress","See who has done what, act on what the AI flags and on suggested changes to how it helps, open a learner's work, and download progress with names.","2026-11-03",[361,363,367,371,375,379],{"id":17,"title":17,"text":362},"**Progress** shows who has done what in a course, and what you may want to look at. Open it during class, or after, before you plan the next lesson.",{"id":364,"title":365,"text":366},"the-grid","The grid","Progress has a row for each learner and a column for each activity, with how many are done. Everyone on the class list has a row, joined or not. Each cell is **done**, **started** or **not opened**. Search names and codes, filter by section, and press **Refresh** to catch up.",{"id":368,"title":369,"text":370},"to-look-at","To look at","Above the grid, **To look at** lists moments from the last 30 days of your learners' work with the AI. Safety replies and calls for a person come first, then the newest. **Safety reply**: the agent's VETTING step stopped a reply for safety. **Needs a person**: the VETTING step asked for a person. **No reply passed review**: no reply passed VETTING in the attempts allowed, so the learner got the last one unapproved. **The AI couldn't answer**: the agent itself said it couldn't answer, or wanted a person. **Rated not helpful**: the learner rated a reply not helpful. **The reply failed**: the lea",{"id":372,"title":373,"text":374},"suggested-changes","Suggested changes","Below **To look at**, **Suggested changes** proposes changes to [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps) in one activity, from what its records show. Choose the activity at the top when the course has more than one with an AI. A suggestion appears when the current version's records show a pattern: **Gives too much away**: at least 30% of 5 or more replies its VETTING step reviewed failed their first review for giving too much away. It suggests one step less help, such as from **Explain when stuck** to **Hints only**. **Replies not passing review**: at least 20% of 5 or more reviewed replies ne",{"id":376,"title":377,"text":378},"a-learner-s-work","A learner's work","Open a cell to see one learner's work in that activity: when they started and finished, and how long they were active their plan, draft and hand-in each answer, **Right** or **Not right**, with tries; **Listen** plays a spoken answer when recordings are kept their highlights and notes their last 20 **Messages with** the agent, typed or spoken, and whether they rated each reply helpful The cell is also where you give extra time. See [Learners and sign-in](\u002Fdocs\u002Fteach\u002Fsign-in).",{"id":380,"title":381,"text":382},"the-download-with-names","The download with names","**Download with names (CSV)** lists each person's name, student ID, section and visits, and each activity done, started or not yet. Use it for your records or gradebook. The file has learners' names and IDs. Keep it somewhere safe. Viewers get **Download (CSV)** instead, by learner code only. The research export also uses learner codes, never names. [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps): set the goal and how far the AI may go, or adjust a suggested change. [Learners and sign-in](\u002Fdocs\u002Fteach\u002Fsign-in): sections, code cards and extra time. [Export](\u002Fdocs\u002Fresearch\u002Fexport): download your course'",{"path":384,"title":385,"description":386,"updated":223,"minutes":35,"sections":387},"teach\u002Fquestion-sets","Question sets","Write, import or draft questions with AI, then use one set as practice in one activity and a quiz in another.",[388,390,394,398,402,406],{"id":17,"title":17,"text":389},"A question set is a list of questions any activity's Questions panel can ask. Build one here, or reuse one across activities. Sets live in [Teach → Question sets](\u002Fdashboard\u002Fquestions), listed by the course and unit that ask them. You can also press **New set** on an activity's Questions panel. Learners never see the answers.",{"id":391,"title":392,"text":393},"write-questions","Write questions","**Start a set.** Press **New question set** and give it a **Title**. **Add a question.** Press **Add a question** and choose its type. **Add help.** Give it up to five **Hints**, an **Explanation**, a **Skill** and **Points**. **Check it.** **Preview** shows the question with its key marked. The types are **Multiple choice**, **Select all that apply**, **Fill in**, **Fill the blanks**, **Put in order**, **Short answer**, **Open answer**, **Program** and **Spoken answer**. Open answers and programs aren't graded automatically. Short and open answers can **Show the model answer after they answer",{"id":395,"title":396,"text":397},"import-questions","Import questions","Press **Import questions**, then paste them or choose a PDF, Word, PowerPoint, Excel or CSV file. Spreadsheets with a header row, lettered choices (Moodle's Aiken format) and JSON are read without AI; **Formats read without AI** has templates. Anything else can be **Read with AI**, which keeps your questions and answers as they are. Untick what you don't want and press **Add**.",{"id":399,"title":400,"text":401},"draft-with-ai","Draft with AI","**Draft with AI** writes questions from your material: **Pick the material.** In **What to draw on**, choose **Your documents** (up to 10, whole or by sections or pages), **A file**, or **Text**, with an optional focus. **Say what to write.** In **What to write**, set how many (up to 30), a level and the types. **Choose who writes.** In **Who writes them**, pick a key, which is billed, and a model. Each question comes with a skill, hints and an explanation, drawn from your material only. Nothing joins the set until you press **Add**. Spoken answers and programs aren't drafted.",{"id":403,"title":404,"text":405},"feedback-and-tries","Feedback and tries","Feedback, tries and hints are set on each activity's Questions panel, so one set can be practice in one activity and a quiz in another: **Right or wrong is shown**: **After each answer** for practice, **At the end** for a quiz, **Never** for a test. **Tries per question**: 0 for any number, up to 10. **Hints**: learners open them one at a time, and each is recorded. Pre-tests and post-tests always get one try and no hints.",{"id":407,"title":408,"text":409},"changing-and-archiving","Changing and archiving","**Asked in** lists every activity that uses the set. A saved change shows in all of them and makes a new version; answers already given keep theirs. To change one activity only, use **Make a copy** on the Question sets page. **Archive** removes a set from the list and from new panels. Activities already using it keep working. [Activities and panels](\u002Fdocs\u002Fteach\u002Factivities): put a set in a Questions panel, alone or in steps. [Following progress](\u002Fdocs\u002Fteach\u002Fprogress): see each answer, and who is stuck on a question.",{"path":411,"title":412,"description":413,"updated":359,"minutes":67,"sections":414},"teach\u002Fexamples-and-packages","Examples and packages","Start from a working example, download any course with or without its files, and move it to another project. Learners and keys never travel with it.",[415,417,421,425,429,433,437],{"id":17,"title":17,"text":416},"An example is a whole course to start from: its activities, the AI beside the work and how far it may go, the questions and rubrics it uses, and the files it reads. A package is the file any course travels as, and each example is one.",{"id":418,"title":419,"text":420},"start-from-an-example","Start from an example","[Docs → Examples](\u002Fdocs\u002Fexamples) has six, each arranged differently: fractions practice with a checked tutor, a PDF reading with a Socratic tutor, an argument essay, English speaking practice by voice, a density lab on an embedded page, and a library research desk that answers from its guides. **Choose one.** Press **Use in my project** on an example, or press **From an example or a file** on [Teach → Courses](\u002Fdashboard\u002Fcourses) and **Use** one. **Choose the AI key.** Pick the key that pays for the course's AI. You can change it later in the course's **Settings**. **Create it.** Press **Crea",{"id":422,"title":423,"text":424},"download-a-course-as-a-package","Download a course as a package","On a course, press **Download as package**, then choose: **The course (.json)**: one file with the course's settings, units and activities, the agents they use (each one's published version) and the question sets their Questions panels ask. Reading panels and knowledge bases stay behind. **With its files (.zip)**: the same, plus each Reading panel's PDF and the knowledge bases its agents search: their uploaded files, and the addresses of their websites, which are read again where it's imported. Up to 25 MB. The project's owners and editors can download it, since it carries the agents' workflow",{"id":426,"title":427,"text":428},"download-an-agent-on-its-own","Download an agent on its own","An agent with a chat page and no course can leave too: on **Agents**, open its **⋯** menu, choose **Export…**, then **With its knowledge (.zip)**. It's a package with no course. Importing it makes the agents and knowledge bases, and no course. See [Chat pages](\u002Fdocs\u002Fbuild\u002Fchat-pages).",{"id":430,"title":431,"text":432},"duplicate-a-course","Duplicate a course","On a course, press **Duplicate** and choose **Into the project**: this one, or another where you're an owner or editor. You get a new draft with the course's settings, look, units and activities, and none of its learners: no class list, sections or anyone's work, and activities with dates start hidden. **In the same project**, the copy is called \"… (copy)\" and uses the same agents, question sets and readings, with the course's AI key. **In another project**, that project gets its own copies of the agents, question sets and knowledge bases, and you choose one of its keys. An agent whose name it",{"id":434,"title":435,"text":436},"bring-a-file-into-a-project","Bring a file into a project","In the project, open **Teach → Courses**, press **From an example or a file**, then **Choose file** under **Upload a package** and pick the `.viable.json` or `.viable.zip`. If you own or edit more than one project, choose **Into the project**. The course arrives as a new draft, with new knowledge bases for its files. An agent whose name the project already uses gets \"(imported)\" after it. If something can't be made, nothing half-made is left open.",{"id":438,"title":439,"text":440},"what-a-package-never-carries","What a package never carries","Learners, the class list, sections, or anyone's work. The course's key, or the first project's IDs. In a `.json` without files: knowledge bases. A Reading panel comes without its PDF and is left out, so add it again, and an agent comes without the knowledge bases it searches. Download it with its files to keep them. The headers of an agent's Fetch URL steps, and email addresses in the course information. Opening dates. Each activity is open, or hidden until you open it. A `.viable.json` can be up to 2 MB and a `.viable.zip` up to 25 MB, with up to 20 agents, 50 question sets and 100 activities",{"id":442,"title":443,"lead":444,"pages":445},"research","Research","Study learning with AI, from consent to a documented dataset.",[446,465,484,507,538,561],{"path":447,"title":448,"description":449,"updated":223,"minutes":35,"sections":450},"research\u002Ffirst-study","Your first study","Start a study from your situation and work through its checks until it is ready to open.",[451,453,457,461],{"id":17,"title":17,"text":452},"Start here the first time you run research on VIABLE. You go from your situation to a study whose checks are all done, ready to open.",{"id":454,"title":455,"text":456},"start-from-your-situation","Start from your situation","Open [Research → Overview](\u002Fdashboard\u002Fresearch). Under **What are you studying?** (or **Start another study**), pick a card. Card What it sets up Button A study in your own course A study on a course you have: follow use, compare units or compare groups **Start with your course** A study in schools Comparing groups on a new course for children, with personal codes and parental permission for minors **Start a study in schools** An online study Comparing groups on a new course for adults, who sign in by the ID in their link (`PROLIFIC_PID`) **Start an online study** Open learning on a chat page ",{"id":458,"title":459,"text":460},"create-the-study","Create the study","**Say what you want to find out.** In **New study**, the card chooses step 1, except for your own course. When comparing units, also choose **The same for everyone** or **Two orders, half the learners each**. **Name it.** Give the study a name your team will recognize. **Choose where learners take part.** Pick **A course you have**, **A new course** (with **Who it is for**: Adults, Teenagers or Children) or, for an observational study, **A chat page**. **It starts with** lists the defaults. **Create it.** Select **Create study**. If you haven't chosen where, the button reads **Create, and add ",{"id":462,"title":463,"text":464},"the-checks","The checks","**Every study:** add the courses it runs in (or where it runs). None of them may be in another open study. **A study that tells or asks people:** add the consent form (or the research notice). **A comparative study:** define at least two conditions, add the activities and make the conditions differ. Have learners sign in the same way every visit, publish an agent, and choose the key that pays for the AI. **Depending on how it assigns:** add the pre-test, make consent about the use of data, add sections to each course, and give each section its condition. **On a chat page:** turn on an agent's ",{"path":466,"title":467,"description":468,"updated":223,"minutes":35,"sections":469},"research\u002Fdesigns","Choosing a study design","Choose whether a study follows use, compares units or compares groups, and where it runs.",[470,472,476,480],{"id":17,"title":17,"text":471},"Choose the kind of study by what changes for learners. What changes for learners Choose The study is Nothing: you follow how a class uses a course, or how visitors use a chat page **Follow how it is used** Observational, with no conditions The same learners have the AI in some units and not others, or one agent and then another **Compare units, with the same learners** Comparative, comparing units: one plan, or two orders drawn at random Learners or whole classes get different versions **Compare groups of learners** Comparative, comparing groups: each learner or section gets one condition Two ",{"id":473,"title":474,"text":475},"common-designs","Common designs","RCT: comparing groups, **With AI** and **Without AI**, each person at random. Cluster randomized: comparing groups, each section at random, with consent about the use of data. Existing classes, not randomized: comparing groups, each section given its condition by you. Two AIs: comparing groups, one condition using another agent, such as one with VETTING. Units with and without AI: comparing units, one plan. Crossover (AB\u002FBA): comparing units, two orders, the second the reverse of the first. Waitlist: comparing groups, the later group without AI until a given unit.",{"id":477,"title":478,"text":479},"where-it-runs","Where it runs","**Where it runs** holds the study's courses and chat pages, up to 40. Courses have known learners, each with its own link, class list and sections: one per school, class or online sample. A chat page can host an observational study. Each device is an anonymous visitor, shown by visitor code (`V-…`). To know who learners are, or to compare, use a course. A comparative study runs on courses only. A course or chat page is in one study at a time, until that study closes. You can remove one only while nobody has taken part through it. **How learners sign in** sets one way for every course while the",{"id":481,"title":482,"text":483},"who-changes-activities","Who changes activities","**Observational:** the activities stay the course's. Teachers keep changing them, and every record carries its activity version. **Comparative:** the first course you add with activities gives the study its design. Its activities move into the study and are changed on **Design**, for every course at once. Other courses must have no activities of their own. A teacher studying their own course changes its activities in the study too. Teachers still set opening times and extra time. Teachers who run a course join the project as editors. [Your first study](\u002Fdocs\u002Fresearch\u002Ffirst-study): start from y",{"path":485,"title":486,"description":487,"updated":223,"minutes":35,"sections":488},"research\u002Fconsent","Consent and taking part","Choose what people are told or asked, handle minors and paper forms, and withdraw someone who asks.",[489,491,495,499,503],{"id":17,"title":17,"text":490},"A study's **Consent** tab decides what people are told or asked before they start, and so whose data the study uses. People who decline or opt out still use the course or chat. Option What people see In the data **Include everyone, without asking** Nothing Everyone **Show a research notice** A notice; on a chat page, a line above the chat opens it Everyone **A notice they can opt out of** The notice, with a way to keep their work out (**Don't use my chats** on a chat page) All but those who opted out **Ask for consent on a form** A form to decide on first Only those who agree Observational stu",{"id":492,"title":493,"text":494},"the-form","The form","Choose **Write it here** (up to 20,000 characters, or **Start from a template**) or **Use your approved PDF** (up to 10 MB). After the study opens, a changed form is an amendment and a new version, listed under **Versions of the form**. With consent, people who decided on an earlier version are asked again on their next visit. With a notice, new arrivals read the new one. Pause the study while an amendment is reviewed. Agreeing covers any recordings the study keeps, so say so in the form.",{"id":496,"title":497,"text":498},"minors-and-paper-forms","Minors and paper forms","**An assent form for minors:** the form asks people their age, and minors agree on the assent form too. A children's course takes everyone as under 18. **Parental permission for minors:** a minor counts in the data once permission is recorded. Upload **The form parents sign** to print. **Paper forms:** as signed forms come back, select **Record** in the **Parental permission** column on **Participants**. **Show them** lists minors who are waiting.",{"id":500,"title":501,"text":502},"withdrawal","Withdrawal","Learners reread the study under **About the study**. With consent, **Leave the study** is final and keeps their earlier work out too. With an opt-out, they can opt out and back in. When someone asks you, select **Withdraw** beside them on **Participants**. Their records, earlier ones too, leave the study's data and coding samples. It is final.",{"id":504,"title":505,"text":506},"what-teachers-see","What teachers see","A course's **Research** tab shows its teachers how many learners take part, by condition. Owners and editors see names beside decisions; viewers see codes. No setting hides decisions from owners and editors. If an instructor must not know who agreed, don't make them one while it matters. How people take part, what consent covers and parental permission are fixed once the study opens. [Conditions and assignment](\u002Fdocs\u002Fresearch\u002Fconditions): set what differs and who gets which. [The research export](\u002Fdocs\u002Fresearch\u002Fexport): see how consent marks every record.",{"path":508,"title":509,"description":510,"updated":223,"minutes":35,"sections":511},"research\u002Fconditions","Conditions and assignment","Set what differs between conditions and who gets which, then open, pause and close a comparative study.",[512,514,518,522,526,530,534],{"id":17,"title":17,"text":513},"Set up a comparative study's **Design** tab, then run the study. For example, half of a Grade 4 math class gets an AI tutor and half doesn't.",{"id":515,"title":516,"text":517},"the-design","The design","**Design** starts with **What this study compares**, then four steps that apply to every course at once. **The activities.** Edit them with the course's own editor. Pre-tests, post-tests and surveys are the same for everyone. **What differs.** Give each condition rules: the AI off, the AI sees nothing of the panels, typed only, replies not read aloud, or another agent. **Who gets which, and when.** Choose who is assigned, how, how it is balanced, and when. **Agent versions.** Opening the study pins each agent at its published version. A new activity starts hidden at every course until its teac",{"id":519,"title":520,"text":521},"conditions-as-rules","Conditions as rules","A study has two to eight conditions, labelled A to H, each with a share from 1 to 10. **Only in some units or activities** adds exceptions, including **Hidden for this condition**. Check **Side by side: what learners get in each condition** before you open. When comparing units, **The plan, unit by unit** (or **The orders**) says whether each unit keeps its AI, has none, or uses another agent.",{"id":523,"title":524,"text":525},"assignment","Assignment","**Who is assigned:** **Each person** or **Each section**. **How:** **At random**, in shuffled blocks balanced by the shares, or for sections, **By you**. **Balanced:** **Across the study**, **Within each section** or **Within each course**. **When:** **When they take part**, **After the pre-test**, or **At first use** (not when comparing units). A condition never changes once given, on any activity or device.",{"id":527,"title":528,"text":529},"pinned-and-protocol-versions","Pinned and protocol versions","Learners keep meeting the pinned agent version as the agent changes. **Use the latest published versions** moves every course on. Once open, every design change and version move makes a new protocol version, shown in the study's header.",{"id":531,"title":532,"text":533},"open-pause-and-close","Open, pause and close","The header's status control runs **Draft → Open ⇄ Paused → Closed**. Each move explains itself first. Move What it does **Open study** People start taking part; assignment starts; agent versions are pinned. **Pause** Learners keep their condition. Nobody new joins the study, and nothing is recorded for it. **Resume** Takes people in and records again. **Close study** Conditions stop; courses return to their own settings; the data stays. **Open again** Reopens with the same conditions and pinned versions. **Delete draft** works only if the study never opened. Once open, how people take part, si",{"id":535,"title":536,"text":537},"participants","Participants","**Participants** shows **Flow**, as a CONSORT diagram counts it; **Balance on the pre-test**, as Hedges' g; **People**, each learner code with decision and condition; and **The study's record** of every decision, assignment and change. [Consent and taking part](\u002Fdocs\u002Fresearch\u002Fconsent): withdrawal and paper forms. [The research export](\u002Fdocs\u002Fresearch\u002Fexport): conditions and versions in your data.",{"path":539,"title":540,"description":541,"updated":223,"minutes":35,"sections":542},"research\u002Fcoding","Coding conversations","Code a sample of messages with a codebook, add model coders, check agreement and decide the final codes.",[543,545,549,553,557],{"id":17,"title":17,"text":544},"Code what people wrote, such as what patrons ask a library's chat page. [Research → Coding](\u002Fdashboard\u002Fcoding) runs in four steps: codebook, sample, code, compare.",{"id":546,"title":547,"text":548},"codebooks","Codebooks","Pick a codebook: **Help-seeking**: executive or instrumental requests (Nelson-Le Gall). **ICAP engagement**: passive, active, constructive, interactive (Chi and Wylie); applying it to chat is an interpretation to state. **Question depth**: shallow, intermediate, deep (Graesser and Person). **Accuracy of replies**: accurate, partly accurate, inaccurate, didn't answer. **What patrons ask**: a library's request categories. **Your own codebook**: 2 to 20 codes. Each code has a label, a definition, examples and near misses. Changing codes after coding makes a new version, with a note on why; each s",{"id":550,"title":551,"text":552},"draw-a-sample","Draw a sample","**Choose the codebook.** Select **New sample** and pick it. **Set where it is drawn from.** Under **Drawn from**, choose the period, study, chat pages or courses, and agents. **Only people included in the study** is on by default. **Draw it.** Enter **Items to sample** (up to 2,000) and, optionally, a **Seed**, then select **Draw the sample**. The same seed and choices over a finished period draw the same items. People who leave the study leave its samples.",{"id":554,"title":555,"text":556},"code-alone-and-with-model-coders","Code, alone and with model coders","On **Code**, code one item at a time with keys 1 to 9. Coding is blind: you see only your own codes until you open **Review & decide**. Any member, viewers included, can code. **Model coders → Add a model coder** adds a model as one more coder, billed to a key you choose. Under **How strictly**, **Only these codes** gives no code when nothing fits, never the closest. **These codes, and suggest new ones** may also propose codes. **About your study** adds context to its prompt. For each item it returns codes, a confidence, a quote kept only if it appears word for word, and a reason. You can read",{"id":558,"title":559,"text":560},"agreement-then-final-codes","Agreement, then final codes","**Agreement** compares every two coders on items both coded: percent agreement, Cohen's κ and a confusion table. **For your methods section** drafts a paragraph to copy. In **Review & decide**, select **Show everyone's codes**, which ends blind coding for you. Pick the final codes or **No code fits**, add a note, and **Decide**. **Use the agreed codes for N** settles items where coders agree. Owners and editors decide. Final codes feed an observational study's **Summary** and `coding_final.csv`. [The research export](\u002Fdocs\u002Fresearch\u002Fexport): codes, codebooks and prompts in your data. [Consent a",{"path":562,"title":563,"description":564,"updated":223,"minutes":35,"sections":565},"research\u002Fexport","The research export","Download a study's documented ZIP, read its consent marks, cite what learners saw, and delete a learner's data.",[566,568,572,576,580,584],{"id":17,"title":17,"text":567},"Get your data out as one documented ZIP, ready to analyze and cite. A study's **Export** tab opens [Research → Export](\u002Fdashboard\u002Fresearch\u002Fexport) with the study chosen. **Choose whose data.** Pick a study, a course, or where (courses, chat pages, your apps on the API). Turn off **Only people included in the study** to get everyone, each record marked. **Choose when and which agents.** Choosing agents leaves out activities without AI. **Choose what goes in.** Turn on **What was written**, **IDs from your systems**, **Recordings** or **Mask condition names** as needed. **Download.** **What's in",{"id":569,"title":570,"text":571},"what-s-in-the-zip","What's in the ZIP","**People and conversations:** `learners.csv`; `turns.csv`, every message and reply with agent version, sources and rating; `agents.csv`. **Work and measures:** `sessions.csv`, `attempts.csv` (pyBKT format), `outcomes.csv`, `writing.csv`, `responses.csv`, `activity_progress.csv` and more. **The study:** `enrollments.csv` (decisions), `assignments.csv`, `flow.csv` (CONSORT counts), `study_events.csv` and `study_documents.csv`. **Coding:** `coding_labels.csv`, `coding_final.csv` and `codebooks.json`. **The dictionary:** `data_dictionary.csv` defines every column and its sources. `manifest.json` r",{"id":573,"title":574,"text":575},"consent-marks","Consent marks","Records from a study carry `study`, `condition` and `participation`. The `participation` mark is `included`, `opted_out`, `declined`, `withdrawn`, `awaiting_permission` or `not_in_study`. Inclusion is decided at export, never by deleting records.",{"id":577,"title":578,"text":579},"masked-conditions-and-recordings","Masked conditions and recordings","Masked conditions appear as A, B and so on, without their names, rules or agents' names. The study's **Data** tab sets the default. Recordings exist only when **Keep recordings of what people say** is on in **Data**. Once the study opens, you can turn it off but not on. `recordings\u002F` holds them only for people whose data counts. `turns.csv` and `responses.csv` name each file.",{"id":581,"title":582,"text":583},"citing-what-learners-saw","Citing what learners saw","**Activities:** records carry `activity_version`, and `manifest.json` lists each version's settings SHA-256. **Agents:** turns carry `agent_version`, and `agents.csv` gives each workflow's SHA-256. For a comparative study, the manifest gives pinned versions and the protocol version. **Forms:** decisions carry their form version, and `study_documents.csv` gives each PDF's SHA-256. **Coding:** `codebooks.json` gives codebook versions, seeds, and each model coder's settings and prompt.",{"id":585,"title":586,"text":587},"deleting-a-learner-s-data","Deleting a learner's data","**Find the person.** Open them in [Teach → Learners](\u002Fdashboard\u002Flearners), or under **Chat page visitors**. **Delete.** Select **Delete data…**, then **Delete their data**. This can't be undone. Their messages, answers, drafts, scores, coding labels, recordings and what agents remembered are deleted, and so is a learner's name. Their study decisions and code stay, as review boards require, and `learners.csv` marks `data_deleted_at`. Owners and editors delete. [Coding conversations](\u002Fdocs\u002Fresearch\u002Fcoding): code a sample before you export. [Consent and taking part](\u002Fdocs\u002Fresearch\u002Fconsent): what ",{"id":589,"title":590,"lead":591,"pages":592},"build","Build agents","Make the AI: its steps, knowledge, checks and chat page.",[593,620,774,804,831,867,879],{"path":594,"title":595,"description":596,"updated":66,"minutes":14,"sections":597},"build\u002Ffirst-agent","Your first agent","Make an agent from a template, try it on your own key, change one step, publish it and read one run step by step.",[598,600,604,608,612,616],{"id":17,"title":17,"text":599},"This page takes you from nothing to a published agent in about ten minutes. Start here before you build anything for learners. An agent is a workflow of steps. It reads a message, can search your documents and check its own reply, then answers. Learners meet the published version in course activities or on its chat page. You need to be an owner or editor of the project. Viewers can look, but not try or change an agent. Trying an agent runs it for real on a project API key with the **Run agents** permission, billed like any request. New keys include it.",{"id":601,"title":602,"text":603},"make-it-from-a-template","Make it from a template","**Open the templates.** In [Build → Agents](\u002Fdashboard\u002Fagents), click **New from template**. **Pick a tutor.** Choose **Safety & review → Tutor with guardrails**. **Before you run** says what a template needs; this one needs nothing. **Name it.** Names are unique in the project. Click **Create & open**. The canvas shows five steps, each card saying what it does. `input` goes to `screen`, a Guardrail step that checks each message for personal data, jailbreaks and harmful content. A message that passes goes to `tutor`, an LLM step, and its answer to `reply`. One that fails goes to `blocked`, a R",{"id":605,"title":606,"text":607},"try-it","Try it","**Open the test chat.** In the right panel, click **Test**, then **Try it**. **Choose who pays.** Click **Options**, choose a key under **Who pays**, and click **Done**. The key is remembered for this project. **Send the example.** Click the example message to put it in the box, then press Enter. Under the reply you see its cost, its steps and how long it took. **Test the guardrail.** Send \"My email is sam@example.com, can you check my essay?\" The guardrail fails it, and `blocked` answers instead.",{"id":609,"title":610,"text":611},"change-one-step","Change one step","**Open the tutor.** Send the example message again, so the last run goes through `tutor`. Click `tutor` on the canvas. **Step settings** says what it does, \"Writes a reply with viable-3\", and shows the last run's time and cost. **See what it gave.** Under **Later steps can use**, `tutor → reply text` shows what it wrote for your last message. **Change its instructions.** Under **Setup**, click **Open editor** under **Instructions**, add \"Use one short paragraph.\", and click **Done**. **Run only this step.** Click **Run step**, then **Run**. It writes a new answer from your last message on the ",{"id":613,"title":614,"text":615},"publish","Publish","Click **Publish**, look over the review, and click **Publish version 1**. The badge now says **Published**, and the agent list shows **Live · v1**. Later edits stay in the draft until you publish again.",{"id":617,"title":618,"text":619},"read-one-run","Read one run","**Open the runs.** Click **Test → Run history**. Your tries are at the top, marked **Dashboard test**. **Open one.** You see its reply, duration, model calls, tokens and cost. **Follow a step.** Under **Run trace**, click `tutor`. **Input** shows its instructions, the message and how many earlier messages went with it. **Output** shows its reply. **Check the guardrail.** Click `screen` to see what it checked and its verdict. That is the loop: change the draft, try it, publish, read what happened. [Steps](\u002Fdocs\u002Fbuild\u002Fsteps): what each step does, and when to use it. [Testing and run history](\u002Fdo",{"path":621,"title":622,"description":623,"updated":359,"minutes":624,"sections":625},"build\u002Fsteps","Steps","Choose the right step for each job, set it up in its panel, and edit, share and version an agent's workflow.",16,[626,628,632,636,640,644,647,651,655,659,662,666,670,674,678,682,686,689,693,697,700,704,708,712,716,720,723,727,731,735,738,742,746,750,754,758,762,766,770],{"id":17,"title":17,"text":627},"Every agent is a workflow of steps on a canvas. This page shows how a step's settings are laid out, says what each step does and which settings matter, then covers editing, sharing and versions.",{"id":629,"title":630,"text":631},"how-a-step-s-settings-are-laid-out","How a step's settings are laid out","Click a step on the canvas to open it in **Build → Step settings**. Every step's panel has the same parts, top to bottom: **The step.** Its type, its ID (other steps refer to it by this, and you can rename it), its status, **Run step** (for a step that can run alone), **?** for how to use it, and **⋯** for the step's menu. **What it does.** A sentence says what the step does as it is set up, such as \"Writes a reply with viable-3\". Its canvas card shows the same sentence. Click a phrase in it to go to that setting. **Setup.** The decisions every author of this step makes, such as its model and ",{"id":633,"title":634,"text":635},"a-setting","A setting","Each setting has an **ⓘ** tip and a **⋯** menu. The menu switches a value to **Use text**, **Use a field**, **Use a condition** or **Edit as expression**. It also has **Reset to default**, **Learn more** and, where they apply, **Copy reference** and **Show on block**. A value reads as text, with earlier steps' fields as chips, such as `tutor → reply text`. Type `{` or `\u002F`, or click the **{ }** button, to insert a field. A condition or an expression reads as a sentence first. **Edit** opens its editor. An expression's editor has **Insert a field…** and **Insert a function…**. Long instructions ",{"id":637,"title":638,"text":639},"find-a-setting-fix-an-issue","Find a setting, fix an issue","**Find a setting** (⌘K on a Mac, Ctrl K elsewhere) searches every setting of the step, set or not. Choose one to go to it. One under **Add option** is added. The status beside the step's ID says **Ready**, or how many issues the step has. Click it to list them, with **Go to setting** when an issue names one. After a message in **Try it**, the panel shows what the step did in that run. [Testing and run history](\u002Fdocs\u002Fbuild\u002Ftesting#what-a-step-did-in-the-last-run) says how to read it and how to run one step alone.",{"id":641,"title":642,"text":643},"start-from-a-common-use","Start from a common use","**Add step** (or `\u002F`) opens the step library, grouped as the sections below. A step with common uses, such as Image, Branch or VETTING, then asks how to start: choose one, such as **Draw what an LLM planned**, or **Blank**. A common use fills in settings you can change.",{"id":645,"title":646,"text":17},"start-and-reply","Start & reply",{"id":648,"title":649,"text":650},"input","Input","Starts the run with the learner's message and the conversation so far. Every run starts at the step marked **Entry**, and one Input is enough. **Options.** **Instructions in Try it** and **Example message** help people who test the agent. **Long conversations** sends the latest messages, or **Summarize when the conversation gets long**: near the models' limit, it folds the oldest messages into a summary, one small model call each time. **Later steps can use.** **Message**, which `{{input}}` reads anywhere, and **Earlier messages**.",{"id":652,"title":653,"text":654},"reply","Reply","Ends a path with the reply the reader sees. **Setup.** **Reply text**: an earlier step's field, such as `tutor → reply text`, or words you write. **Options.** **Picture or audio** attaches an Image or Speech step's address. **Result data for your app** is JSON your app reads beside the reply; readers never see it. **Sources in Chat Completions replies** lists cited sources in the text, for chat clients that show only text.",{"id":656,"title":657,"text":658},"ask-user","Ask user","Replies with a **Question** and waits. The next message in the conversation carries on from here, and later steps read **The user’s answer**. It needs a conversation kept on the server; otherwise the question is just the reply.",{"id":660,"title":661,"text":17},"ai","AI",{"id":663,"title":664,"text":665},"llm","LLM","Writes a reply, or returns named fields, with one call to a chat model. It can't search or use tools; an Agent step can. **Setup.** **Model**, **Instructions**, and **What it reads**: **Earlier turns** (on by default), **Reference data**, **This conversation’s pictures**, and a **Custom input** in place of the learner's message. **Answer format.** **Plain text**, or **Named fields** that later steps and Branch rules read by name. **Options.** **Image inputs**, and model settings such as **Temperature** and **Reasoning effort**. **Later steps can use.** **Reply text** (`{{tutor.content}}`), or ",{"id":667,"title":668,"text":669},"agent","Agent","Lets a model choose tools until it can answer. **Setup.** **Model**, **Instructions**, **Use knowledge** and **What it reads**. **Options.** **Remember the user**, **Read web pages**, **Hand off to agents**, and **Most tool calls** (5 by default). **Later steps can use.** **Final answer** (`{{helper.content}}`) and **Tool calls**. Each tool call is one more model call, so an Agent step is slower and costlier than fixed steps, and its path varies. A rule that must always hold, such as \"never give the answer\", needs a check: a VETTING or Guardrail step.",{"id":671,"title":672,"text":673},"image","Image","Draws or changes pictures. **Setup.** **Model**, then **What to draw**: **One prompt**, or **A list from an earlier step**, usually an LLM step's list. With a list, choose it under **List**: each item becomes its own picture, three at a time. **Prompt** describes one scene, or adds to each item's prompt. **Options.** **Pictures per prompt** draws one scene several times in one model call. **Most pictures a turn** caps a list (4 by default, up to 6). **Create or change** draws new pictures or changes one, and **Picture to change** takes the number the learner sees. **Remember for later turns** ",{"id":675,"title":676,"text":677},"speech","Speech","Reads **Text** aloud in a **Voice** and passes on the recording's **Audio address**. Attach `{{speak.url}}` under a Reply's **Picture or audio**. **Speed** is an option.",{"id":679,"title":680,"text":681},"transcribe","Transcribe","Turns a **Recording** into text: the one sent with the message, or a recording's address. **Language** and **Words to expect** help it hear. Later steps read **Transcript**. Put it before the steps that use what was said.",{"id":683,"title":684,"text":685},"sub-agent","Sub-agent","Hands the message to another published **Agent** of this project and uses its **Reply**. **Message to send** replaces the message. Its model calls count toward this run.",{"id":687,"title":688,"text":17},"checks-and-review","Checks & review",{"id":690,"title":691,"text":692},"vetting","VETTING","Has an independent reviewer check each reply against your rules, and revise it until one passes. **Setup** holds **Checks against**, the **Reply model** and **Answer to review**. **Attempts** is under **When something goes wrong**. Each of the **Outcomes**, **Passed**, **Unverified** and **Needs attention**, goes to its own step. See [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting).",{"id":694,"title":695,"text":696},"guardrail","Guardrail","Checks text once and goes to `pass` or `fail`. **Setup.** **Checks**: **Personal data** (no model call), **Jailbreak \u002F prompt injection**, **Harmful content**, **Off topic** (with an **Allowed topic**) and **Custom policy**. **Text to check** is the current message, or custom text such as `{{tutor.content}}` to screen a reply. **Later steps can use.** **Passed**, **Findings** and, with **Personal data**, **Text with personal data masked**. **Ways to start.** **Screen the learner’s message**, **Keep to a topic** or **Mask personal data**.",{"id":698,"title":699,"text":17},"routing-and-logic","Routing & logic",{"id":701,"title":702,"text":703},"branch","Branch","Chooses a path by a field from an earlier step, with no model call. **Condition.** One condition goes to **Yes** when it holds and **No** when it doesn't. **Cases.** Checked from the top; the first that holds decides, and **Otherwise, go to** takes the rest. Each case is a set of rules (a field, a test and a value, joined by all or any), or an expression after **Edit as expression**. A list's rules can test its items, such as **has more than … items**, **has an item whose … is empty** or **is empty without items whose … is**. **Name this case** labels a case's connection on the canvas and its ",{"id":705,"title":706,"text":707},"router","Router","A model sorts each request into one of your named **Routes** (up to 12, each saying when to choose it). Anything unclear takes `fallback`. It costs one model call; when an earlier field already decides, a Branch does it for free. Later steps read **Chosen route** and **Reason**. **Ways to start:** **Questions or tasks** or **Coursework or admin**.",{"id":709,"title":710,"text":711},"for-each","For each","Runs the same steps once for every item of a **List**, one after another, then carries on with what each pass kept. Connect **for each item** to the first step to repeat, and the last one back to the For each step. Steps inside read `{{item}}` and `{{item_number}}`, or the **Name for one item** you choose. **At most** caps the items (10 by default, up to 25). **Keep from each item** says what each pass keeps, such as `{{review.json.correct}}`. **when done** carries on with **What each item kept** (`{{check.values}}`). Steps inside can't reply or ask. For pictures, an Image step with a list is ",{"id":713,"title":714,"text":715},"loop","Loop","Repeats steps **A number of times** or **While a condition holds**, then goes to `done`. A run visits a Loop at most 25 times. **Counter name** names the pass number later steps read. **Ways to start:** **Repeat a number of times** or **Repeat until something holds**.",{"id":717,"title":718,"text":719},"parallel","Parallel","Runs 2–6 **Paths** at the same time, then carries on at the join step once all finish. It saves time; each path still spends its own model calls.",{"id":721,"title":722,"text":17},"knowledge-and-memory","Knowledge & memory",{"id":724,"title":725,"text":726},"knowledge","Knowledge","Searches 1–5 **Knowledge bases** for the current message, or for what you write in **Search for**. Later steps read **Passages with sources** (`{{search.context}}`) and **Passages found**. **Passages to return** is 5 by default, up to 20. See [Knowledge bases](\u002Fdocs\u002Fbuild\u002Fknowledge).",{"id":728,"title":729,"text":730},"memory","Memory","Recalls or keeps lasting facts about the user across conversations. Its **Action** is **Recall relevant memories**, **Save a specific fact** or **Learn facts with a model**, which makes one model call; put that after the step that writes the reply. It needs the user's identifier or a server conversation. **Ways to start:** **Recall what it knows about the user**, **Save one fact** or **Learn from the conversation**.",{"id":732,"title":733,"text":734},"state","State","Sets **Variables** for this run, or keeps them for later turns with **Keep values → For this conversation**. Read them anywhere as `{{vars.name}}`. Pictures need no State step. **Ways to start:** **Keep a value for later turns** or **Count attempts**.",{"id":736,"title":737,"text":17},"web-and-data","Web & data",{"id":739,"title":740,"text":741},"fetch-url","Fetch URL","Reads one public HTTPS **Web address**, or sends JSON to an API. It doesn't search the web or run a site's JavaScript. Later steps read **Web page as Markdown**, **Succeeded** and **HTTP status**. **Ways to start:** **Read a web page** or **Send JSON to an API**.",{"id":743,"title":744,"text":745},"component","Component","Runs another team's method as a step, from the manifest its authors publish: what the component does, the learner data it receives, the hosts it contacts, its license and its evidence. A component on VIABLE runs at a public HTTPS address; one that runs as an MCP server runs with `viable-engine serve` on your own machine instead. **Setup.** **Component manifest**: paste the JSON, or **Upload a manifest file**. The panel shows what it receives, the hosts it contacts, its risk and license, or what stops it running. Its slot must be step. **What it sends** has one row per field the manifest receiv",{"id":747,"title":748,"text":749},"values","Values","Works out named values from earlier steps, each able to use the ones above it, with no model call. In Code and the API its type is `transform`. The panel shows one row per value, split into **Used by other steps** and **Used only here**. A row shows the value's name, what it works out and, after a Try it run, what it held. Open a row to edit it. It says which steps use it, such as `reply → Reply text`. **Add value** adds one. **Edit as JSON** shows them all as JSON. Later steps read each one by name, such as `{{summary.text}}`. A long calculation reads best here, one value per row. A value is ",{"id":751,"title":752,"text":753},"edit-on-the-canvas","Edit on the canvas","Drag from an output dot (`next`, `pass`…) onto the next step. Every run starts at the step marked **Entry**. A step's **⋯** menu has **Make this the entry step**. The chip beside **Tools** says **Ready**, or how many issues to fix. **Tips** under it suggest ways to make a working workflow simpler, such as a list instead of copied steps. **Tidy** lays the steps out left to right. A connection back to an earlier step, a loop's way back, goes round underneath, and the steps a For each step repeats sit in a frame. Never put a password, API key or token in a step. Every project member can read it, ",{"id":755,"title":756,"text":757},"note","Note","A **Note** step is a sticky note for collaborators, from **Add step → Annotate**. Notes never run.",{"id":759,"title":760,"text":761},"groups-and-blocks","Groups and blocks","To keep a large workflow readable, open a step's **⋯** menu and choose **Put in a new group**. Name the group on its frame and fold it into one block. Groups never change what runs. **What it does and its settings**, a button on a group's frame, opens the **Block** panel, and so does a click on a folded block. **What it does** is a sentence of up to 280 characters, shown on the folded block. To show a setting on the block, open a step inside and choose **⋯ → Show on block** on that setting. Up to 8 settings show on the folded block with their values. Change them in the Block panel's **Settings",{"id":763,"title":764,"text":765},"code-view-and-the-assistant","Code view and the assistant","**Build → Code** shows the workflow as JSON. Edit it or paste JSON from an AI tool, then click **Apply JSON**; until then you can't publish. **AI prompt + JSON** writes a prompt for your own AI tool. **Build → Assistant** answers questions about the agent and proposes changes, each waiting for **Apply to draft** or **Discard**. **Ask about this step**, in a step's panel, starts a question about that step.",{"id":767,"title":768,"text":769},"work-together","Work together","An agent has one shared draft that saves as you go. Edits from teammates, the API and the VIABLE CLI land in it too. **Share & invite** invites people to the whole project as **Editor** or **Viewer**. **Undo** undoes only your own edits.",{"id":771,"title":772,"text":773},"versions","Versions","**History → Save a version** marks a point to return to. **View & restore versions** lets you **Preview** one or **Restore** it into the draft. **Publish** makes the draft a new numbered version, the one learners, chat pages and apps use. Its review lists problems by step, with **Go to step**. **Tools → Discard changes** puts the draft back to the live version. **Tools → Unpublish agent** stops new runs and keeps the draft. [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting): check every reply against rules you state. [Knowledge bases](\u002Fdocs\u002Fbuild\u002Fknowledge): give an agent documents to search and cite. [Testing an",{"path":775,"title":691,"description":776,"updated":66,"minutes":67,"sections":777},"build\u002Fvetting","Have an independent reviewer check each reply against your rules, and decide what learners see when none passes.",[778,780,784,788,792,796,800],{"id":17,"title":17,"text":779},"A VETTING step has an independent reviewer check each reply against your rules. Use it when replies must follow rules you can state. VETTING (Verification and Evaluation Tool for Targeting Invalid Narrative Generation) comes from classroom research ([a middle school study](https:\u002F\u002Fdoi.org\u002F10.1016\u002Fj.caeai.2026.100646)). Use it for a tutor that never gives the answer away, or an assistant that says only what its sources say.",{"id":781,"title":782,"text":783},"how-it-works","How it works","The **reply model** writes a reply. The **reviewer model** checks it in a separate call and gives a verdict and a reason. A failed reply goes back with that reason, until one passes or the **Attempts** run out. The reviewer's rules live in the step, apart from the reply model's instructions, so a learner can't talk the reviewer out of them. It reads the reply and nothing the learner wrote. With an answer key, a reply that confirms an answer as the learner's own passes, one that supplies it fails, and one that calls a wrong answer right fails. A learner who has the answer is told so. Code compa",{"id":785,"title":786,"text":787},"fast-or-careful","Fast or careful","A learner waits for every round, one reply and one review, with nothing on the page until one passes. With a strict reviewer that can be half a minute. How much care is worth how much waiting depends on your learners, so it is yours to set, and the defaults are only a place to start. To change Do this And A shorter wait Fewer **Attempts**, or a faster **Reviewer model** More replies end unverified, or pass with a lighter review More care More **Attempts**, or a stricter **Reviewer model** A longer wait, and more cost No wait at all Take the VETTING step out Nothing checks replies before learne",{"id":789,"title":790,"text":791},"set-it-up","Set it up","When you add a VETTING step, choose how to start: **Check replies against a guideline**, **Don’t give a question’s answer away**, **Check an earlier step’s answer**, or **Blank**. Then: **Choose what it checks against.** Under **Setup → Checks against**, pick **A guideline** or **A question and its answer key**. The **Answer key** is seen only by the reviewer. **Set the reply model.** Choose a **Reply model**. **Add option → Reply instructions** replaces its default prompt. Safety rules are always added. **Choose the first answer.** Under **Answer to review**, pick **The reply model writes it*",{"id":793,"title":794,"text":795},"outcomes","Outcomes","**Passed**: the reviewer approved the reply. **Unverified**: no reply passed, or a review couldn't run. Later steps get the last reply, not approved. **Needs attention**: the reply model found the conversation unsafe, such as self-harm. It wrote a caring reply instead, which isn't reviewed.",{"id":797,"title":798,"text":799},"what-learners-see","What learners see","Learners see only what the Reply step on each path says. They never see drafts, the reviewer's reasons or the answer key. Decide what the **Unverified** path says. The VETTING templates give a fixed message instead of the unapproved reply. **Insights** lists replies that didn't pass.",{"id":801,"title":802,"text":803},"when-a-teacher-sets-how-the-ai-helps","When a teacher sets How the AI helps","In a course activity, a teacher uses **How the AI helps** to say how far the AI may go. Say a Grade 6 teacher writes \"hints only\". The reply model gets it as instructions, headed \"From the teacher of this activity\". The reviewer checks each reply against it too. The agent profile's **What it reads** decides which steps get it. With **Only as template variables**, nothing is added; use `{{context.workspace.teaching}}` yourself. [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps): what a teacher tells the AI in an activity. [Testing and run history](\u002Fdocs\u002Fbuild\u002Ftesting): read each VETTING round in a run's ",{"path":805,"title":806,"description":807,"updated":66,"minutes":14,"sections":808},"build\u002Fknowledge","Knowledge bases","Add documents and websites to a knowledge base, and have agents search them and cite their sources.",[809,811,815,819,823,827],{"id":17,"title":17,"text":810},"A knowledge base holds documents and websites your agents search and cite. Use one when answers must come from your own material, such as a syllabus or a library's guides. Knowledge bases live in [Build → Knowledge](\u002Fdashboard\u002Fknowledge). Agents search only knowledge bases in their own project.",{"id":812,"title":813,"text":814},"add-documents-and-websites","Add documents and websites","**Make it.** Click **New knowledge base**, name it, and choose how it searches (below). **Add files.** On **Files**, drop files or a folder, up to 15 MB each. **Paste text** adds notes or an FAQ. **Add websites.** On **Websites**, choose **Specific pages**, **A whole website** or **Pages linked from one page**. **Choose how often to re-check.** **Check for changes** runs from **Daily** to **Yearly**, or **Only when I sync**. The default is **Monthly**. Files can be PDF, Word, PowerPoint, Excel, Markdown, text, CSV, HTML, JSON or images. **Specific pages** and **A whole website** take up to 20,",{"id":816,"title":817,"text":818},"choose-how-it-searches","Choose how it searches","Search How it works Best for **Hybrid** Keywords plus meaning, reranked Paraphrased questions, and questions in other languages **Keyword only** Keywords, with no AI calls Predictable results on consistent terms",{"id":820,"title":821,"text":822},"settings-that-change-answers","Settings that change answers","In the knowledge base's **Settings**: **Passage size**: Short, Standard or Long (about 600, 1,200 or 2,400 characters). **Leave out text a website repeats on its pages**: drops navigation and footers from passages. **Let readers open cited files**: cited uploaded files link to the file for 7 days. Leave it off for internal or licensed files, and readers see only their names. Web pages always link.",{"id":824,"title":825,"text":826},"how-agents-search","How agents search","Two steps search, and they suit different questions. A **Knowledge** step searches its **Knowledge bases** once, for the current message or what you write in **Search for**, and passes on numbered passages: **Passages to return** is 5 by default and up to 20. Put `{{search.context}}` in an LLM step's instructions. Tell it to answer only from those sources and cite them as [n]. Branch on `{{search.count}}`, which is 0 when nothing matched. The **Answer from my documents** template works this way. An **Agent** step with **Use knowledge** searches as often as it needs and reads whole sections. Us",{"id":828,"title":829,"text":830},"citations","Citations","Citations are numbered in the order a reply mentions them, one per page or file. On a chat page, each reply shows its sources. **Test search** shows exactly which passages an agent would receive for a question. **Build an agent with it** starts the agent designer with this knowledge base chosen. [Steps](\u002Fdocs\u002Fbuild\u002Fsteps): the Knowledge, LLM and Agent steps in context. [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting): have a reviewer check replies against your sources. [Chat pages for websites](\u002Fdocs\u002Fbuild\u002Fchat-pages): what visitors see of your sources.",{"path":832,"title":833,"description":834,"updated":66,"minutes":835,"sections":836},"build\u002Ftesting","Testing and run history","Try your draft on a key you choose, run one step alone, read every step of any run, and see what people ask the published agent.",5,[837,839,841,845,849,853,857,861,865],{"id":17,"title":17,"text":838},"Try your draft before learners meet it, check it against tests, then read what happened in any run.",{"id":605,"title":606,"text":840},"**Test → Try it** runs the unpublished draft, so you don't need to publish to test. **Who pays.** Every message is a real run on the key under **Options → Who pays**. It needs the **Run agents** permission and must allow your steps' models. A stopped run still bills the model calls it started. **Memory.** **Options → Memory** chooses **Continue a conversation** (recommended), **Independent messages** or **Send history from this browser**. **Test as.** **Options → Test as** sends a person's identifier, such as `student-042`, as your app would. **Reference data.** Material sent with each message",{"id":842,"title":843,"text":844},"what-a-step-did-in-the-last-run","What a step did in the last run","After a message in Try it, click a step on the canvas. Its panel shows what the step did in that run: **Under its sentence:** **Last run** with its time and cost, **Last run failed**, or **Not reached in the last Try it run**. **Under a setting:** **Last run:** and what the setting worked out to, such as an LLM step's instructions with their fields filled in. **Under Later steps can use:** what each output held. A Values step shows each value's. **In a Branch step:** **last run** marks the case the run took. **earlier version** means the workflow changed since that run, so these values come fr",{"id":846,"title":847,"text":848},"run-one-step","Run one step","**Run step**, at the top of a step's panel, runs only that step, once, as it is set up now, with what the last Try it run gave it. Use it to try a change to one step without sending a message through the whole agent: new instructions, another model, a different list of pictures. To see what learners get, send a message in Try it. **Start from a run.** Send a message in Try it, with a key under **Options → Who pays**. **Open the step.** Click it on the canvas, change what you want to try, and click **Run step**. **Run it.** **Starts from** names the run, and **Who pays** names the key. Click **",{"id":850,"title":851,"text":852},"run-history","Run history","A run is one execution of an agent. **Test → Run history** lists runs newest first. Filter by **Where runs came from**, such as **Chat page**, **Course** or **Dashboard test**, and by **How runs ended**. You can also filter by result data, for example `needs_human: true`. **Export** downloads the latest 200 as CSV or JSONL.",{"id":854,"title":855,"text":856},"a-run-s-trace","A run's trace","A run shows its reply, duration, model calls, tokens and cost. **Run trace** lists the steps in order. A step's **Input** is what it acted on; its **Output** shows the result (**Pretty** or **Raw JSON**) with the **Provider calls**. A VETTING step lists each round's verdict and the **Reviewer**'s reason.",{"id":858,"title":859,"text":860},"tests","Tests","To check a change against what learners might say before you publish, keep tests and run them: see [Agent tests](\u002Fdocs\u002Fbuild\u002Fagent-tests).",{"id":862,"title":863,"text":864},"insights","Insights","**Test → Insights** shows what people ask the published agent over 7, 30 or 90 days, without your tests, and flags answers that **need a look**: **Failed**, **Not answered**, **Needs a person** and **Review didn't pass**. **Topics** groups recent questions, with **What the sources seem to miss**. For the middle two, have your Reply step return `\"status\": \"not_found\"` or `\"needs_human\": true` in **Result data for your app**.",{"id":728,"title":729,"text":866},"Memory is what an agent keeps about a person between conversations, up to 200 facts each, from a **Memory** step or **Remember the user** in an Agent step. A person is the `user` your app sends, or a chat page's visitor. **Test → Memory** lists who the agent remembers and what. Deleting a fact, or using **Forget everything about** a person, is permanent. [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting): what each outcome of a VETTING step means. [Learners and traces](\u002Fdocs\u002Fconnect\u002Flearners-and-traces): name your app's users and keep each run's trace.",{"path":868,"title":869,"description":870,"updated":871,"minutes":35,"sections":872},"build\u002Fagent-tests","Agent tests","Keep cases of what learners might say and what a good reply does, and run them on a version before you publish.","2026-11-01",[873,875],{"id":17,"title":17,"text":874},"Before learners meet a change, run your agent on cases you keep and let a judge model say whether each reply does what it should. Use it after editing a prompt, adding a step, or switching a model. A test is a moment a learner might bring and what a good reply does. **Test → Tests**, beside Run history, keeps up to 25 per agent; editors write and run them, and the project reads them. **A test.** The learner's message, or a short conversation ending with it, and what a good reply does. Optionally, what it must not do (such as \"give the final answer\"), a name, and reference data. **Save as a tes",{"id":876,"title":877,"text":878},"from-the-cli","From the CLI","Your own AI can run them too: `viable developer agents tests list|set|run-case|runs`, naming the key that pays and confirming each run, like a test run. In `tests set`, a test's `pictures` are `[{\"url\": \"https:\u002F\u002F…\", \"description\": \"what it shows\", \"label\": \"Mom\"}]`, or `{\"reason\": \"safety\", \"description\": \"…\"}` for one that wasn't drawn. See [The CLI and Agent Skills](\u002Fdocs\u002Fproject\u002Fcli). [Testing and run history](\u002Fdocs\u002Fbuild\u002Ftesting): try a draft and read any run's steps. [VETTING](\u002Fdocs\u002Fbuild\u002Fvetting): a reviewer that checks each reply before learners see it.",{"path":880,"title":881,"description":882,"updated":359,"minutes":67,"sections":883},"build\u002Fchat-pages","Chat pages for websites","Put a published agent on a website as a link, a QR code or a chat button, within limits you set.",[884,886,890,894,898,902,906,910],{"id":17,"title":17,"text":885},"A chat page puts a published agent on any website as a link, a QR code and a chat button. Visitors need no account, so it suits a library's help desk. It always runs the published version. To know who learners are, use a course instead. Librarians can start from **Overview → Open learning on a website**.",{"id":887,"title":888,"text":889},"turn-it-on","Turn it on","**Open the tab.** Publish the agent, open **Connect → Chat page**, and click **Set up chat page**. **Choose the key.** Under **Key & limits**, pick the key it **Runs on**. Every message is a run on this key. **Turn it on.** Set limits and websites (below), then click **Turn on chat page**.",{"id":891,"title":892,"text":893},"share-it-or-add-it-to-a-site","Share it or add it to a site","**Share** gives the link and QR code. **Add it to a site** asks **Where will people chat?** and gives the steps for each place. On any website, paste the script before `\u003C\u002Fbody>`. Copy yours from the tab: In LibGuides, add a Media \u002F Widget asset to one guide, or use Admin → Look & Feel → Custom JS\u002FCSS for every guide. Canvas removes scripts, so it gets a frame. Your page can open the chat with a question already asked, for example from its own search box when nothing matches: The question is sent once the chat has loaded, as if the visitor picked it. This site's docs search does the same with i",{"id":895,"title":896,"text":897},"limits-and-allowed-sites","Limits and allowed sites","**All visitors, per day**: 10–5,000 messages, default 300. **Daily cap** sets what it may spend a day. **Each visitor, per minute**: 1–30, default 8. **Each visitor, per day**: 0 for no limit. **Longest message**: 200–4,000 characters. Daily counts reset at midnight UTC. A school behind one address can send 400 messages a minute, and each visitor keeps their own limit. For 30 learners, 300 a day is ten messages each. **Only these websites** (up to 20) limits where the button and frame appear, not the link.",{"id":899,"title":900,"text":901},"recordings-and-attachments","Recordings and attachments","**What it records**: each device is an anonymous visitor with a visitor code (V-…), listed in [Teach → Learners](\u002Fdashboard\u002Flearners). **Keep recordings** saves spoken messages privately. **Voice**: with a Transcribe step, **Visitors can speak** appears. With a Speech step, so does **Play answers aloud**. **Photos & files**: up to 4 files of 8 MiB each, when every step that reads the message can read them.",{"id":903,"title":904,"text":905},"what-it-makes-public","What it makes public","Anyone with the link, or on an allowed site, can chat with your agent on your key. The profile is public. Replies show their sources, but uploaded files show only their names unless the knowledge base allows opening them. The page hides your workflow, but a model can repeat its instructions. Write nothing there you wouldn't show a visitor. Visitors can't see each other's conversations. People sharing one computer and browser are one visitor. **Pause** takes the link and embeds offline. **Turn off chat page** also clears the settings.",{"id":907,"title":908,"text":909},"the-agent-profile","The agent profile","The profile is how the agent presents itself, on its chat page and in courses. In **Connect → Profile → Edit profile**, set the **Greeting**, up to four **Suggested questions** and the **Notice** saying what the AI is. Changes show without publishing.",{"id":911,"title":912,"text":913},"take-it-to-your-own-server","Take it to your own server","On **Agents**, open the agent's **⋯** menu, choose **Export…**, then **With its knowledge (.zip)**. No course is needed. Owners and editors can download it. **It holds** the published version, its knowledge bases with their files, the text of their website pages as VIABLE last read them, its profile and chat page settings, and the agents it calls. **It leaves out** the page's key, conversations, logs and the headers of Fetch URL steps. Anything else it had to leave out is listed in its `notes`. It can be at most 25 MB. **It runs** on your own server with the VIABLE Engine, with the same page, ",{"id":915,"title":916,"lead":917,"pages":918},"connect","Connect an app","Run agents from your own software and your AI assistant.",[919,938,969,1002,1025,1046,1071],{"path":920,"title":921,"description":922,"updated":223,"minutes":14,"sections":923},"connect\u002Ffirst-run","Your first run","Create an API key, run a published agent from your server for one of your users, and find that run in VIABLE.",[924,926,930,934],{"id":17,"title":17,"text":925},"In fifteen minutes you create an API key, run a published agent from your own server for one of your users, and find that run in VIABLE with its learner and its trace. Start here when your app has its own sign-in and screens and uses VIABLE agents behind them. You need a VIABLE Developer account, a project, and a published agent in it. To make one, see [Your first agent](\u002Fdocs\u002Fbuild\u002Ffirst-agent). For a first call to a model from your code, with no agent yet, the [Developer quickstart](\u002Fquickstart) takes five minutes.",{"id":927,"title":928,"text":929},"create-a-key-and-find-the-agent","Create a key and find the agent","**Create an API key.** On **Overview**, choose **Connect your app → Create an API key**, or open [Operate → API keys](\u002Fdashboard\u002Fkeys) and choose **Create key**. **Choose a service key.** In a shared project, pick **Service key**: it belongs to the project and keeps working when members change. The owner creates these. **Name it and keep its scope.** Name it after the app and environment, such as \"Writing App · production\". Under **Scopes**, keep **Run agents** (`agents:execute`). **Store the secret.** Copy it from the dialog and store it on your server as `VIABLE_API_KEY`. **Find the agent's ",{"id":931,"title":932,"text":933},"run-the-agent-for-one-user","Run the agent for one user","`user` is your app's ID for the person. The `X-Platform-*` headers name your app. The same call in Node.js 18 or newer: A successful run returns `200` with `object: \"agent.run.result\"`, a `conversation_id` (`agtc_…`), a `run_id` (`agtr_…`), `output`, `usage` and `cost`. The reply is `output.text`. An error returns an `error` object with `message`, `type` and `code`. Branch on `code`. The key stays on your server. Never put it in browser JavaScript, an app bundle or a repository. Your web or mobile code calls your server, and only your server calls VIABLE.",{"id":935,"title":936,"text":937},"see-the-run-in-viable","See the run in VIABLE","**Open the run.** Open the agent and choose **Test → Run history**. Your run is at the top, marked **API · runs**, with a learner code (`L-…`). Open it to see each step, what it received and returned, and every VETTING round. **Meet the learner.** Follow the learner code to [Teach → Learners](\u002Fdashboard\u002Flearners). The learner shows \"Known by My App (my-app:student-0001)\", and the turn carries your app's name. **Find the request.** In [Operate → Request logs](\u002Fdashboard\u002Flogs), open **More filters** and set **Platform ID** to `my-app`. You see the run and each model call inside it. Within second",{"path":939,"title":940,"description":941,"updated":223,"minutes":35,"sections":942},"connect\u002Flearners-and-traces","Learners and traces","Name your app's users as learners, keep their conversations, and store a trace of what each run did.",[943,945,949,953,957,961,965],{"id":17,"title":17,"text":944},"Name the person behind each run so VIABLE keeps them as a learner, and record what each run did. Set this up before real people use your app.",{"id":946,"title":947,"text":948},"name-your-users-with-user","Name your users with user","Send `user` on every run: a stable, opaque ID your server derives from the account it verified, up to 128 characters. Namespace it (`my-app:7f3a9c21`) or hash it. Never use a name, an email, a shared account or an unchecked ID. The first run with a new `user` creates a learner in the project, with a learner code (`L-…`). Later runs, on any agent in the project, add turns to it. [Teach → Learners](\u002Fdashboard\u002Flearners) lists them with your app's name and your ID. Viewers see that ID too. **Research → Export**, with **Your apps (API)**, exports their turns, and what you [send as learning data](\u002Fd",{"id":950,"title":951,"text":952},"label-task-groups-with-learner-group","Label task groups with learner_group","Send `learner_group` with `user` to label a turn's task group, such as `\"Grade 8\"`. It is kept on the turn, never in the prompt. **Teach → Learners → Group** filters by it, and `turns.csv` includes it. It never changes a course section or study condition. Without `user`, it returns 400.",{"id":954,"title":955,"text":956},"keep-the-conversation","Keep the conversation","By default VIABLE keeps the conversation. The first run returns a `conversation_id`. Store it per person, thread and agent, and send it with each new `input`. Never share one among people. Only the key that started it can continue it. To move a thread your app already has, seed it: send `messages` with two or more entries, the last from the user, and no `conversation_id`. The response carries the new `conversation_id`. To keep history yourself, send `history_mode: \"client\"` with the full `messages`.",{"id":958,"title":959,"text":960},"ask-for-the-trace","Ask for the trace","Add `\"include\": [\"trace\"]` to `\u002Fruns` or `\u002Fjobs`. The result then has `trace`. `duration_ms` is wall-clock time. Don't add up steps: parallel branches overlap. `steps[]` lists each step's `node_type`, `status`, `model`, `tokens`, `cost` and `error`. `vetting[]` has one entry per VETTING step: `outcome` (`passed`, `unverified`, `attention`), `attempts` (more than one means a rewrite) and each round's `verdict` and `feedback`.",{"id":962,"title":963,"text":964},"read-a-run-back","Read a run back","`GET \u002Fv1\u002Fagents\u002F{slug}\u002Fruns\u002F{run_id}`, with the key that made the run, returns it with its `trace`. Runs from another key, a course or a chat page return 404 `run_not_found`.",{"id":966,"title":967,"text":968},"what-to-store","What to store","Keep one record per turn on your server: For failed requests, also keep the HTTP status, `error.code` and `error.request_id`. A trace holds what learners wrote. Keep it on your server, never in the learner's browser, and never show it to learners. [Send learning data](\u002Fdocs\u002Fconnect\u002Flearning-data): add what learners do, write and score in your app. [Background jobs and retries](\u002Fdocs\u002Fconnect\u002Fjobs-and-retries): retry a turn without running it twice. [Export](\u002Fdocs\u002Fresearch\u002Fexport): download your app's turns for analysis.",{"path":970,"title":971,"description":972,"updated":223,"minutes":35,"sections":973},"connect\u002Flearning-data","Send learning data","Record what learners do in your app (events, drafts, ratings and scores) so it joins their learner record and the research export.",[974,976,980,982,986,990,994,998],{"id":17,"title":17,"text":975},"VIABLE sees only your app's agent runs. Send the rest from your server: what learners did and wrote, how they rated replies and how they scored. It joins the learners your runs already name.",{"id":977,"title":978,"text":979},"what-you-get","What you get","**Insights** in [Build → Apps](\u002Fdashboard\u002Fapps): active learners, time writing, pauses, pastes, drafts handed in, ratings and learner groups, day by day. **A timeline per learner** in [Teach → Learners](\u002Fdashboard\u002Flearners): their work in your app by activity, and their scores. **The research export** with your app's data (`writing.csv` measures how each draft was written), ready for [Coding](\u002Fdocs\u002Fresearch\u002Fcoding). To have your coding AI wire it up, paste this message in your app's code:",{"id":196,"title":197,"text":981},"**Give the key the scope.** In [Operate → API keys](\u002Fdashboard\u002Fkeys), choose **Edit key** on your app's key and turn on **Record learning data** (`learning:write`). **Tag every call.** Send `X-Platform-ID`, such as `writing-app`. Events and drafts are filed under your app's ID and the activity, so they need it. **Reuse your `user`.** The `user` you send on runs is the same learner here. A new `user` creates a learner.",{"id":983,"title":984,"text":985},"send-events","Send events","`POST \u002Fv1\u002Flearning\u002Fevents` takes one learner's events in one activity, up to 200 per call. Use VIABLE's event kinds, such as `writing.progress` (keys, pastes, pauses, active time), `writing.snapshot` (the text at a pause), `writing.submit` and `panel.view`, or `app.event` for your own, with a flat `data` object. Each kind keeps only its own fields. The response lists any event it skipped, by index.",{"id":987,"title":988,"text":989},"save-drafts","Save drafts","`POST \u002Fv1\u002Flearning\u002Fdrafts` saves the `draft`, and the `plan` if your app has one, as they are now: send the whole text each time. Add `submit: true` when the learner hands it in.",{"id":991,"title":992,"text":993},"rate-replies","Rate replies","When a learner marks a reply helpful or not, send `POST \u002Fv1\u002Fagents\u002F{slug}\u002Fruns\u002F{run_id}\u002Frating` with `rating` set to `1`, `-1`, or `null` to take it back. It uses **Run agents** and works only for runs this key made.",{"id":995,"title":996,"text":997},"send-scores","Send scores","`POST \u002Fv1\u002Flearning\u002Foutcomes` records one `measure`, such as a rubric total, for up to 1,000 users. A user that no run, event or draft has named yet comes back in `unmatched`.",{"id":999,"title":1000,"text":1001},"where-it-lands","Where it lands","**Research → Export**, with **Your apps (API)**, or one app from its Insights: events in `workspace_events.csv`, drafts and how they were written in `writing.csv`, ratings in `turns.csv`, scores in `outcomes.csv`. A run with `user` also keeps its `context` as the turn's `workspace`. [Build → Apps](\u002Fdashboard\u002Fapps): the checklist marks each kind your app has sent, and **Insights** counts it. Consent is your app's to handle: tell learners what it records before you send anything. Keep `user` opaque, as on your runs: never a name or an email. [Learners and traces](\u002Fdocs\u002Fconnect\u002Flearners-and-trace",{"path":1003,"title":1004,"description":1005,"updated":223,"minutes":14,"sections":1006},"connect\u002Fjobs-and-retries","Background jobs and retries","Pick the right route for an agent call, run long work as a background job, and retry without running a turn twice.",[1007,1009,1013,1017,1021],{"id":17,"title":17,"text":1008},"Every published agent answers on three routes. Pick the one that fits the work, and retry an unanswered request safely.",{"id":1010,"title":1011,"text":1012},"choose-a-route","Choose a route","All three run the same agent and share keys, `user` and `learner_group`. `\u002Fruns` `\u002Fjobs` `\u002Fchat\u002Fcompletions` You send `input`, optional `conversation_id` The same, plus `Idempotency-Key` `model` (the slug) and `messages` You get The result, or a stream `202` and a job to poll `choices[0].message.content` Time 60 s, or the agent's 10–120 5 minutes or more As `\u002Fruns` In the dashboard API · runs API · background job API · chat completions Start with `\u002Fruns` when someone is waiting; `\"stream\": true` streams the reply. Use `\u002Fjobs` for long research, image generation, or a host that cuts requests sh",{"id":1014,"title":1015,"text":1016},"run-a-background-job","Run a background job","The `202` has an `id` (`agtj_…`), a `status_url` and a `Retry-After` header. Wait that long between polls. `queued` or `running`: poll again. If the platform restarts while running a turn, it picks the turn up again itself (or, for an agent that only calls models, runs it again once), so `running` may last a few seconds longer. Don't resend it. `completed`: `result` is the body `\u002Fruns` returns. `failed`: `error` has the `code`. To try again, submit a new job with a new key. A job not started within 15 minutes fails with `agent_job_expired`, and one that stopped before its reply was delivered w",{"id":1018,"title":1019,"text":1020},"retry-safely","Retry safely","Send an `Idempotency-Key` with every turn: 8–128 letters, digits, dots, underscores, colons or hyphens. Save the route, body and key on your server. If no answer arrives, resend the same body to the same route with the same key. **The run completed:** you get the same result. **Still going:** 409 `agent_request_pending`. Retry later with the same key. **The body differs:** 409 `idempotency_conflict`. **It started, then failed** (such as `provider_timeout`): the key keeps that error. Retry with a new key. **Rejected before it started** (validation, 401, 403, 404, 413, rate or spending limits): ",{"id":1022,"title":1023,"text":1024},"limits-and-errors","Limits and errors","**Size:** 12 MiB per request, `context` up to 128 KiB, up to 400 `messages`. **Rate:** a key's per-minute limit covers all your users: thirty learners in one class share it. **429:** wait for `Retry-After`. For token, cost or credit limits, don't retry in a loop. Branch on `error.code`, and never show an error as the agent's reply. The [API reference](\u002Fdocs\u002Fapi) lists every code. [Learners and traces](\u002Fdocs\u002Fconnect\u002Flearners-and-traces): keep each turn's run ID and trace. [Keys, credit and costs](\u002Fdocs\u002Fproject\u002Fkeys-and-costs): set a key's limits and see who pays.",{"path":1026,"title":1027,"description":1028,"updated":223,"minutes":35,"sections":1029},"connect\u002Fembed","Your page in an activity","Put your own web page inside a course activity, record what learners do in it, and let the AI beside it know.",[1030,1032,1036,1040,1042],{"id":17,"title":17,"text":1031},"Put your own page, such as a fractions game or a lab simulation, inside a course activity. An agent can sit beside it, and what learners do there goes into the research export. Courses are set up in the dashboard or with the CLI, not with API keys. Your page never learns who the learner is; VIABLE records its events with the learner.",{"id":1033,"title":1034,"text":1035},"set-up-the-activity","Set up the activity","**Serve your page over https.** Let `https:\u002F\u002Fpage.viablelearning.org` frame it, for example with `Content-Security-Policy: frame-ancestors https:\u002F\u002Fpage.viablelearning.org`. **Add the panel.** In [Teach → Courses](\u002Fdashboard\u002Fcourses), open the course, add an activity, and add an **Embedded page** panel with your page's address. **Choose what the agent is shown.** If the activity has an agent, pick **Its events**, **Nothing** or **Custom**. **Say how it ends.** Turn on **It says when the learner is done** if your page reports completion. Otherwise learners press \"I'm done\". Serve the page from e",{"id":1037,"title":1038,"text":1039},"add-the-bridge","Add the bridge","Any page works as it is. To record what happens and tell the agent, add a few `postMessage` calls. The panel's **Connect your page (optional)** offers them to copy: `session` is a random ID for this visit. Ignore other origins. `type` What happens `event` Kept as an event: a `verb` and optionally `object`, `score`, `max_score`, `success`, `completion`, `response`, `duration_ms` and `data`. `state` What your page shows now, kept at most every 5 seconds. `ask` Puts `text` in the chat box for the learner to send. Nothing is sent for them. `hello` Asks for the `hello` again if your page started li",{"id":240,"title":241,"text":1041},"With **Its events**, when a learner asks while your panel is open, the step writing the reply sees your page's last ten events and its latest state. **Custom** sets 0–20 events and whether the state is included. The agent's **Profile** sets where this enters the prompt, and any step can read it as `{{context.workspace}}`. Learners are told on the panel what the agent sees.",{"id":1043,"title":1044,"text":1045},"what-gets-recorded","What gets recorded","Events and states are kept with the learner, course, activity and activity version, at every recording level. The research export has them in `workspace_events.csv`. When a study runs in the course, its consent and conditions apply as in any activity. [Activities](\u002Fdocs\u002Fteach\u002Factivities): build the activity around your page. [Export](\u002Fdocs\u002Fresearch\u002Fexport): download the events with the course's data.",{"path":1047,"title":1048,"description":1049,"updated":223,"minutes":35,"sections":1050},"connect\u002Fapps","Build → Apps","See what each app's learners did, what it still needs, and connect any app with one message to your coding AI.",[1051,1053,1055,1059,1063,1067],{"id":17,"title":17,"text":1052},"Connect your app once and its learners get VIABLE's research tools. Check this page after your first run and before launch.",{"id":977,"title":978,"text":1054},"**Insights**: learners, sessions, AI use and ratings, writing, events and learner groups, day by day. **A timeline per learner** in [Teach → Learners](\u002Fdashboard\u002Flearners): runs, work and scores. **The research export** of your app's data alone, documented for R, Python or SPSS. **[Coding](\u002Fdocs\u002Fresearch\u002Fcoding)** of what learners asked, by your team or a model. **[VETTING](\u002Fdocs\u002Fbuild\u002Fvetting)**: a reviewer checks each reply against your rules. **Studies**, once your page runs [in a course activity](\u002Fdocs\u002Fconnect\u002Fembed). Data your app sends alone is never in a study.",{"id":1056,"title":1057,"text":1058},"connect-it-with-your-coding-ai","Connect it with your coding AI","**Paste the message** below into Claude Code, Codex or any agent with a terminal, in your app's code. **Review its changes.** It installs the VIABLE CLI and Skill if missing, then wires your server to send `user`, `X-Platform-ID`, traces and learning data. **Check it here.** Your app appears with its checklist and **Insights**.",{"id":1060,"title":1061,"text":1062},"what-it-shows","What it shows","[Build → Apps](\u002Fdashboard\u002Fapps) lists every app that sent requests or learning data with the project's keys in the last 90 days. There is nothing to register. An app is its `X-Platform-ID`. Untagged requests are grouped by key. **Insights** counts 30 or 90 days as the research export does; hover a measure for its definition. **Download its data** exports that app alone. **Its requests** opens Request logs filtered to the app. Chat pages, courses and the Playground aren't apps.",{"id":1064,"title":1065,"text":1066},"the-checklist","The checklist","A missing step links to its guide: **Calls with a project key**, **Runs an agent**, **Names its users** (`user`), **Tags its requests** (`X-Platform-ID`), **Labels task groups** (`learner_group`, if you need it), then the learning data rows of [Send learning data](\u002Fdocs\u002Fconnect\u002Flearning-data).",{"id":1068,"title":1069,"text":1070},"tag-your-requests","Tag your requests","Send these optional headers from your server on every call. Choose them before launch: logs can't be re-tagged. Header Use it for `X-Platform-ID` A stable ID for your app, such as `writing-app`. `X-Platform-Name` Its readable name, such as `Writing App`. `X-Operation-Type` The feature, such as `give-feedback`, from a small, stable set. `X-Request-ID` Your own correlation ID, such as a session and turn. Values are cut at 240 characters (`X-Request-ID` at 200). Never put names or emails in them. [Request logs](\u002Fdashboard\u002Flogs) filter by **Platform ID**, **Operation** and your request ID; the res",{"path":1072,"title":1073,"description":1074,"updated":359,"minutes":835,"sections":1075},"connect\u002Fassistants","Connect your AI assistant","Add one address to your assistant to find standards and misconceptions and write teaching instructions. Add a key to check replies and run agents.",[1076,1078,1082,1086,1090,1094,1098],{"id":17,"title":17,"text":1077},"Ask the AI assistant you already use to find a standard, name the misconceptions that get in its way and write How the AI helps with you. It takes one address, with nothing to install and no key to get. This is an [MCP](https:\u002F\u002Fmodelcontextprotocol.io) server. To have your AI build courses and agents in VIABLE as you, use [the CLI](\u002Fdocs\u002Fproject\u002Fcli) instead.",{"id":1079,"title":1080,"text":1081},"add-the-address","Add the address","Claude Code: Codex CLI: Cursor, in `.cursor\u002Fmcp.json`: VS Code, in `.vscode\u002Fmcp.json`: Another assistant that connects to MCP servers over HTTP takes the same address. Then ask it: Find the standard for adding fractions with unlike denominators and the misconceptions that get in its way. Then write teaching settings that ask first, and show me the instructions the AI would get.",{"id":1083,"title":1084,"text":1085},"what-it-can-do","What it can do","With no key: **Find a standard.** Search K-12 standards by words or code, and read one with its learning components (`find_standards`, `get_standard`). **Find misconceptions.** Math misconceptions from Eedi's graph, with the skills they hinder (`find_misconceptions`). **Write teaching instructions.** Turn teaching settings into the instructions a reply step gets (`teaching_instructions`). **Search public goods.** The catalog of open datasets, benchmarks and tools for K-12 AI (`find_public_goods`). **Check a document.** An agent file, a course package or teaching settings, against VIABLE's form",{"id":1087,"title":1088,"text":1089},"add-a-key-for-the-rest","Add a key for the rest","**Create a key.** In [Operate → API keys](\u002Fdashboard\u002Fkeys), choose **Create key**. Under **Scopes**, keep **Model requests** (`model:request`) to check replies and **Run agents** (`agents:execute`) to run agents. **Set its limits.** Give it a **Monthly cost (USD)** you're comfortable with. Your assistant decides how often to call these tools. **Keep the secret out of the chat.** Store it as `VIABLE_API_KEY` where your assistant starts, and send it as a bearer token. Claude Code: Codex CLI: Cursor, in `.cursor\u002Fmcp.json`: VS Code, in `.vscode\u002Fmcp.json`: Model calls are billed to the key's projec",{"id":1091,"title":1092,"text":1093},"hosted-or-on-your-computer","Hosted or on your computer","The same tools also run on your own computer, as `viable-harness mcp` from the [VIABLE Harness](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fdocs\u002Fmcp.md). Choose by where your data should go. Hosted On your computer **Setting up** An address Node.js 22 and the Harness **What you send** Goes to VIABLE Stays on your computer, apart from what you send to your model endpoint **Model calls** VIABLE's, billed to your key Your own endpoint and key **Standards** Yes, through VIABLE's access No **Misconceptions, public goods, teaching instructions, validation, components** Yes Yes **",{"id":1095,"title":1096,"text":1097},"what-viable-keeps","What VIABLE keeps","**Request logs keep no tool's arguments or results.** A log row holds the time, the status and how long it took, your address, your assistant's user agent and what Cloudflare reports about the connection, the request's headers with credentials removed, and the tool's name. With a key, also the key's prefix and project; for a call that used a model, its models, tokens and cost. **A run of your agent is kept like any other.** `run_published_agent` runs your agent the way the API does, so its message and reply are in the agent's **Test → Run history**. Send only what your project may keep. **Noth",{"id":1022,"title":1023,"text":1099},"What happens What it means What to do `429` from your address One address may send 10,000 requests an hour to the API, with a key or without Wait for `Retry-After` `429` from your key The key's **Requests per minute** Wait for `Retry-After`, or raise the limit `413` Over 256 KiB without a key, or 1 MB of arguments with one Send a smaller document, or use the local server `401` The key is wrong, revoked or expired. A bad key never falls back to no key Fix the key, or remove it `check_reply failed: it needs a VIABLE API key` That tool needs a key Add a key `This API key doesn't have the … scope`",{"id":1101,"title":1102,"lead":1103,"pages":1104},"project","Run a project","People, keys, costs and logs.",[1105,1128,1151,1178],{"path":1106,"title":1107,"description":1108,"updated":223,"minutes":35,"sections":1109},"project\u002Fmembers-and-roles","Members and roles","Make a shared project, invite people in the right roles, and hand the project to a new owner.",[1110,1112,1116,1120,1124],{"id":17,"title":17,"text":1111},"Decide who works in your project and what each person can do.",{"id":1113,"title":1114,"text":1115},"make-a-shared-project","Make a shared project","A project is your team's space: its agents, courses, studies, keys and logs belong to it. Open [Account → Projects](\u002Fdashboard\u002Fprojects), choose **New project**, name it, and choose **Create project**. You become its owner. Your personal project, shown with a lock, is private and can't be shared.",{"id":1117,"title":1118,"text":1119},"roles","Roles","Owner Editor Viewer See keys (no secrets), logs, usage, agents and members Yes Yes Yes See learners' names, student IDs and personal codes Yes Yes Codes only Change agents, courses, question sets and studies Yes Yes No Invite and manage members; manage shared provider keys Yes If the owner allows No Create service keys, transfer, archive; pays for usage Yes No No An editor can change every course in the project, not only their own. For a teacher who should run one course only, [share that course](\u002Fdocs\u002Fteach\u002Fsharing) instead. A research assistant who only codes in **Research → Coding** can be ",{"id":1121,"title":1122,"text":1123},"invite-people","Invite people","In the project's **Members**, choose **Invite member**. Type a name or email address, pick **Viewer** or **Editor** as the **Role**, and choose **Invite member**. Someone with an account gets access at once. Others show as **Pending** until they sign in with that email. Each member's **…** menu can pause or remove them. Removing a member, or making an editor a viewer, revokes their personal API keys; if an app or course runs on one, make it a service key first.",{"id":1125,"title":1126,"text":1127},"transfer-ownership","Transfer ownership","**Start the offer.** Open **Settings → Transfer ownership**, or the member's **…** menu. **Choose the new owner.** Pick an active member who has signed in as the **New owner**. **Send it.** Read **After they accept** and choose **Send offer**. They have 7 days to accept, under **Review offer** on the project's **Overview**. Until then you stay the owner, keep paying, and can **Cancel offer**. When they accept, you become an editor. Every API key moves to their account, so new requests use their credit and their personal provider keys. Past usage stays with you, and unused credit doesn't move. ",{"path":1129,"title":1130,"description":1131,"updated":223,"minutes":35,"sections":1132},"project\u002Fkeys-and-costs","Keys, credit and costs","Give each app, course and chat page a key paid the way you intend, and know who pays for every request.",[1133,1135,1139,1143,1147],{"id":17,"title":17,"text":1134},"Give each piece of work its own key, set its limits, and know who pays. The project's owner pays for every request.",{"id":1136,"title":1137,"text":1138},"api-keys","API keys","An API key, made in [Operate → API keys](\u002Fdashboard\u002Fkeys), is how code calls VIABLE. A **service key** belongs to the project and keeps working when people leave. Only the owner makes one. Use it for apps, courses and chat pages. A **personal key** belongs to the member who made it. It stops if they are paused, leave or become a viewer. Choose **Create key** (editors see **Create personal key**), set its **Scopes**, **Limits** and **Model access**, and keep the secret on your server. Before someone leaves, the owner can choose **Make service key** on their key. **Scopes:** courses and chat pag",{"id":1140,"title":1141,"text":1142},"a-course-s-or-chat-page-s-key","A course's or chat page's key","**A course:** its **Settings → The AI → The AI key: who pays for the course's AI**. **AI messages per learner per day** can cap each learner. **A chat page:** the agent's **Chat page → Key & limits → Runs on**. Use a service key, one per course or chat page, so **Usage** shows what each costs. A whole class shares the course's key. A low daily token limit can stop the AI for all 30 learners mid-lesson.",{"id":1144,"title":1145,"text":1146},"provider-keys-and-key-pools","Provider keys and key pools","Under [Account → Billing → Provider keys](\u002Fdashboard\u002Fprovider-keys), add your own keys for OpenAI, Google Gemini, UF NaviGator or another OpenAI-compatible endpoint. Requests then bill your provider, not VIABLE credits, with no code changes. Keys for one provider form a **key pool**: when one runs out or is rejected, the next takes over. A lab can pool its grant keys.",{"id":1148,"title":1149,"text":1150},"credit-and-who-pays","Credit and who pays","[Account → Billing → Credits](\u002Fdashboard\u002Fcredits) shows one balance. Every project you own draws from it, whichever member's key made the request. Request Paid by A catalog model whose provider has a pool set to **Every … model request** That pool A catalog model with no such pool The owner's VIABLE credits A model called as `@name\u002F\u003Cmodel>` That pool A pool with no key available An error, or credits if the pool chose **Use VIABLE credits** The project's **Overview** shows the result under **How this project is billed**. Costs are estimated from listed model prices; an agent run costs the sum o",{"path":1152,"title":1153,"description":1154,"updated":223,"minutes":35,"sections":1155},"project\u002Flogs-and-usage","Request logs and usage","Find any request your project made, see the model calls inside an agent run, export logs, and track what it all costs.",[1156,1158,1162,1166,1170,1174],{"id":17,"title":17,"text":1157},"Find a request, see what an agent run called, and track what your project spends. Use it when a learner reports a strange reply, or to plan a budget.",{"id":1159,"title":1160,"text":1161},"find-a-request","Find a request","[Operate → Request logs](\u002Fdashboard\u002Flogs) keeps every API call in the current project: time, status, endpoint, model, key, tokens, cost, latency, and what was sent and returned. Logs aren't deleted on a schedule. **Pick a range.** Choose when it happened. **Search.** Type a VIABLE request ID, a run ID, or your app's `X-Request-ID` in **Find a request, run, or client ID**. **Filter.** Narrow by **API key**, **Endpoint**, **Model**, **Status** or **Errors only**. **More filters** adds **Platform ID**, **Operation**, **Agent**, and Playground or agent test requests. **Open it.** Choose a row to s",{"id":1163,"title":1164,"text":1165},"a-run-s-model-calls","A run's model calls","An agent run is one row. Expand it to see each model call's step, model, status and share of the time, and choose a call for its details. **Trace** opens the run step by step.",{"id":1167,"title":1168,"text":1169},"paid-by","Paid by","Once the project has key pools, **Paid by** filters to **VIABLE credits**, **Any key pool**, or one pool. The summary line then separates credit cost from requests on your keys.",{"id":1171,"title":1172,"text":1173},"export","Export","**Set the filters.** The export holds every request that matches, with its payloads. **Start it.** Choose **Export**, pick **CSV** or **JSONL**, and start it. **Download it.** It is prepared on the server, so you can leave the page. It stays under **Recent exports** for 24 hours. One export runs at a time. Viewers can export too. Request logs and their exports include what learners wrote to the AI, and every member can read them. A CLI log export can leave the text out with `--no-bodies`.",{"id":1175,"title":1176,"text":1177},"usage","Usage","[Operate → Usage](\u002Fdashboard\u002Fusage) shows requests, errors, tokens and estimated cost for the current project. Set a **Date range**, from **7 days** to **All time** or **Custom**. Filter by **API key**, **Model** and, with key pools, **Paid by**. **Group by** **Day**, **API key**, **Model**, **Endpoint** or **Paid by**. Choose a total at the top to chart it. In **Breakdown**, choose a row to filter the page, or **Logs** to see its requests. Filter to one key to see how much of each limit it has used. **Who paid** splits requests and cost between credits and each pool. For **All time**, or more",{"path":1179,"title":1180,"description":1181,"updated":191,"minutes":35,"sections":1182},"project\u002Fcli","The CLI and Agent Skills","Let your own AI work in VIABLE as you: build a course, set up a study or connect an app, with one message to set it up.",[1183,1185,1189,1193,1197],{"id":17,"title":17,"text":1184},"Your own AI can do VIABLE work for you: build a course, set up a study, connect an app. The VIABLE CLI (`viable`) acts as you, with your access, and its Agent Skill teaches your AI to use it safely.",{"id":1186,"title":1187,"text":1188},"let-your-ai-set-it-up","Let your AI set it up","Paste this one message into your AI: Where to paste it: **If you don't code**: Claude Cowork in the Claude desktop app, ChatGPT Work in the ChatGPT app, or Gemini with **Task** chosen under the prompt box. Start a new task and paste the message. **If you build software**: Claude Code, Codex, Google Antigravity, or any agent with a terminal. **It installs the CLI and the Skill.** The guide tells your AI what to install where it works, and how to check it. **You approve the sign-in.** It shows you a link and a code; you approve in your own browser. It never sees your password. **Then ask in your",{"id":1190,"title":1191,"text":1192},"what-to-ask-it","What to ask it","You Ask, for example Teacher \"Make a course for Period 2 with a writing activity and a tutor that gives hints only, then send me the preview link.\" Researcher \"Set up an observational study on that course with consent, and upload our approved consent form.\" App developer \"Connect our server to VIABLE: name each user, tag our requests, send learning data, then check Build → Apps.\" Owner \"Show last week's cost by key and model.\" It can't do more than your role allows, since VIABLE checks every request. Every change needs an exact `--confirm` value, and the Skill tells your agent to show you what",{"id":1194,"title":1195,"text":1196},"install-it-yourself","Install it yourself","Prefer the terminal? You need Node.js 22 or newer. On a remote machine or a cloud agent, sign in with `viable auth login --device`. `viable agent install --target` takes `codex`, `claude`, `kimi` or `qwen`. `viable help` lists the commands. `viable doctor` checks the setup, and `viable update --with-skill` keeps both current. Signing in never creates an account: register at https:\u002F\u002Fdevelopers.viablelab.org first. [Resources → CLI & Agent Skills](\u002Fdashboard\u002Fcli) has the same steps.",{"id":1198,"title":1199,"text":1200},"agents-and-these-docs","Agents and these docs","The Skill reads the [API guide](https:\u002F\u002Fdevelopers.viablelab.org\u002Fagents\u002Fviablelab-api.md) for current routes, fields and errors. Any agent can start from [llms.txt](https:\u002F\u002Fdevelopers.viablelab.org\u002Fllms.txt), which lists every page here as Markdown. The CLI and its Skill are open source under the MIT License. [Members and roles](\u002Fdocs\u002Fproject\u002Fmembers-and-roles): what your role lets your AI do. [Send learning data](\u002Fdocs\u002Fconnect\u002Flearning-data): connect an app, or let your AI do it. [Your first class](\u002Fdocs\u002Fteach\u002Ffirst-class): what your AI builds, step by step.",{"id":1202,"title":1203,"lead":1204,"pages":1205},"resources","Open resources","The public learning-science resources VIABLE builds on, and their licenses.",[1206,1233],{"path":1207,"title":1208,"description":1209,"updated":223,"minutes":14,"sections":1210},"resources\u002Flearning-commons","Learning Commons","How VIABLE uses Learning Commons' Knowledge Graph for standards, skills and math misconceptions, what it keeps, and how to credit it.",[1211,1213,1217,1221,1225,1229],{"id":17,"title":17,"text":1212},"VIABLE reads academic standards and durable skills from [Learning Commons](https:\u002F\u002Flearningcommons.org)' Knowledge Graph, an open dataset of standards, skills and what they break into. This page explains what VIABLE takes from it, when, and what you owe it when you share your work.",{"id":1214,"title":1215,"text":1216},"what-viable-uses","What VIABLE uses","The Knowledge Graph connects standards and skills to smaller pieces of learning. VIABLE uses five parts of it, all in an activity's **How the AI helps**: In the Knowledge Graph In VIABLE Academic standards **K-12 standard**: every state, Washington, D.C. and Common Core, pre-K to grade 12 Learning components **It breaks into**: the skills inside a standard Prerequisite relations **Standards before it**: what the AI can go back to Carnegie Skills Progressions **Durable skill**: collaboration, communication and critical thinking, at four levels Eedi Misconceptions Graph **Misconceptions to liste",{"id":1218,"title":1219,"text":1220},"when-viable-asks-and-what-it-keeps","When VIABLE asks, and what it keeps","**You choose a standard or a skill.** While you search for a standard, VIABLE asks Learning Commons, and keeps each answer for a day. Durable skills and misconceptions come from VIABLE's own copy. **VIABLE keeps a copy.** What you pick, with the pieces you leave ticked, is saved in the activity's version, with its source and license. **Learners never wait on it.** When a learner sends a message, the agent reads that saved copy. Learning Commons isn't asked again. So a change at Learning Commons never changes what an activity's AI was already given, and your research export says exactly what ea",{"id":1222,"title":1223,"text":1224},"looked-up-or-copied","Looked up, or copied","Learning Commons publishes the graph two ways: an API, and release files of about a gigabyte. VIABLE uses each where it works best. **Standards are looked up.** The API finds a standard from a teacher's own words, and stays current without VIABLE storing it. The saved copy means learners' runs never depend on it. **Durable skills are copied.** The API doesn't search them yet, so VIABLE carries the Carnegie Skills Progressions from the release files: 31 indicators, each at four levels. **Misconceptions are copied, and matched by words.** The release files carry Eedi's 8,117 math misconceptions ",{"id":1226,"title":1227,"text":1228},"crediting-it","Crediting it","The Knowledge Graph is licensed [CC BY 4.0](https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002F), and Carnegie and Eedi share their parts under the same license. VIABLE shows the attribution beside every standard, skill and misconception, and keeps it in the saved copy. When you publish materials or data that include their text, credit them, for example: \"Standards from the Learning Commons Knowledge Graph, CC BY 4.0.\" or \"Misconceptions from the Eedi Misconceptions Graph v1.0, (c) 2026 Eedi Ltd., via Learning Commons, CC BY 4.0.\"",{"id":1230,"title":1231,"text":1232},"not-teaching-k-12","Not teaching K-12?","A durable skill fits learners of any age, and a goal in your own words fits anyone: a university seminar, an adult literacy class, a library workshop. See [How the AI helps](\u002Fdocs\u002Fteach\u002Fai-helps). [Standards, skills and misconceptions](\u002Fdocs\u002Fteach\u002Fstandards-and-skills): choose them for an activity. [The research export](\u002Fdocs\u002Fresearch\u002Fexport): what each activity version used, in one download.",{"path":1234,"title":1235,"description":1236,"updated":223,"minutes":35,"sections":1237},"resources\u002Flicenses","Licenses and credit","What VIABLE builds on, under which licenses, and how to credit it when you publish materials, papers or data.",[1238,1240,1244,1248,1252],{"id":17,"title":17,"text":1239},"VIABLE builds on open research and open data. This page lists what it uses, under which license, and what to credit when you share a course, a paper or a dataset made with it.",{"id":1241,"title":1242,"text":1243},"what-viable-builds-on","What VIABLE builds on","Resource What VIABLE uses it for License [Learning Commons Knowledge Graph](\u002Fdocs\u002Fresources\u002Flearning-commons) Standards, what they break into, and the standards before them [CC BY 4.0](https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002F) [Eedi Misconceptions Graph v1.0](\u002Fdocs\u002Fresources\u002Flearning-commons), (c) 2026 Eedi Ltd., via Learning Commons Math misconceptions to listen for, and the skills they hinder [CC BY 4.0](https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F4.0\u002F) [VETTING](https:\u002F\u002Fgithub.com\u002Fhichipli\u002Fvetting-python) Checking each reply against rules and an answer key MIT (reference implementation) [Th",{"id":1245,"title":1246,"text":1247},"where-the-credit-already-appears","Where the credit already appears","You don't have to add attribution inside VIABLE. It is already there: **In the activity editor**, beside every standard, with the date it was retrieved, and under the misconceptions. **In the activity's version**, which keeps each standard's and misconception's source, license and attribution. **In the research export.** `manifest.json` keeps each activity version's settings, standard and misconceptions included. When a version used them, `README.md` says how to credit them. **In the data dictionary**, measures name the papers they come from.",{"id":1249,"title":1250,"text":1251},"when-you-publish","When you publish","**Materials with a standard's or a misconception's text.** Add a line such as: \"Standards from the Learning Commons Knowledge Graph, CC BY 4.0.\" or \"Misconceptions from the Eedi Misconceptions Graph v1.0, (c) 2026 Eedi Ltd., via Learning Commons, CC BY 4.0.\" **A paper about an agent that uses VETTING.** Cite the [VETTING study](https:\u002F\u002Fdoi.org\u002F10.1016\u002Fj.caeai.2026.100646). **A dataset from the research export.** Keep `README.md`, `manifest.json` and `data_dictionary.csv` with it, so others know what each column means and where it comes from. Learners' data isn't open data. Share an export only",{"id":1253,"title":1254,"text":1255},"your-own-materials-and-the-models","Your own materials and the models","What you and your learners put into VIABLE is covered by VIABLE's [Terms of Service](https:\u002F\u002Fwww.viablelab.org\u002Fterms-of-service) and [Privacy Policy](https:\u002F\u002Fwww.viablelab.org\u002Fprivacy-policy). Replies come from the model you choose, and that model's provider sets its own usage terms. [Models](\u002Fmodels) lists each provider. When a question set or a knowledge base holds someone else's material, its license still applies. Keep its credit with it, for example in the question set's description or the document itself. [Learning Commons](\u002Fdocs\u002Fresources\u002Flearning-commons): what VIABLE takes from it, wh",{"id":1257,"title":1258,"lead":1259,"pages":1260},"harness","Harness","What a good teacher has in mind, around the AI that teaches. What a classroom needs, around the AI that builds.",[1261,1292,1332,1363,1430,1457,1517,1536,1543,1586,1634],{"path":1262,"title":1263,"description":1264,"updated":13,"minutes":67,"sections":1265},"harness\u002Foverview","VIABLE Harness","Give any AI agent what a good teacher has in mind, before it replies.",[1266,1268,1272,1276,1280,1284,1288],{"id":17,"title":17,"text":1267},"**Give any AI agent what a good teacher has in mind, before it replies.** Building a tool for learners with a coding agent instead?\n[That's the other side of the Harness](\u002Fdocs\u002Fharness\u002Ffor-builders): what the AI that builds needs to know about a classroom. In the field's terms, a harness is everything around a model that decides what it knows, what it may do, when it stops and\nwhether the result is any good. General harnesses are tuned for writing code. This one is tuned for teaching, and it goes\naround the agent you already have. A learner writes *\"i got 2\u002F5\"* for 1\u002F2 + 1\u002F3. Before your model",{"id":1269,"title":1270,"text":1271},"four-ways-in","Four ways in","**Your assistant, with nothing to install.** A Skill teaches an assistant the moves, and MCP gives it the tools: **Any app, in any language, by changing one URL.** Each call gets what its moment needs, each reply is checked before your\napp sees it, and a record feeds the teacher's page. Nothing else changes: [Recipes](\u002Fdocs\u002Fharness\u002Fwith-your-stack) for curl, Python, Node, LangChain, LangGraph, LlamaIndex, the OpenAI Agents SDK and the Vercel\nAI SDK, each one run as written. **Your own code, with three calls** around whatever writes the reply: **Another harness.** If yours can add to the system",{"id":1273,"title":1274,"text":1275},"what-it-does","What it does","When Part The question How Before the reply **Context** What should it have in mind at this moment, and nothing more? what the teacher decided ([How the AI helps](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fskills\u002Fwriting-how-the-ai-helps\u002FSKILL.md)); standards and misconceptions from [open resources](\u002Fdocs\u002Fharness\u002Fresources); teaching moves, each with its source; what this learner just did Before the reply **Rules** What may it see and do here? how far it may help; which tools are off, removed rather than only told; no learner's identity; answer keys that reach only the rev",{"id":1277,"title":1278,"text":1279},"not-only-replies","Not only replies","Each kind of teaching work is a task, prepared the same way. For a task that isn't a conversation, `turn.messages` is the whole\nrequest, and `runTask` sends it and checks the answer: Task What it does `tutoring` A reply to a learner, as above `adapt-plan` Summarizes a class's evidence against the teacher's goal, and suggests changes with the evidence beside each `differentiate` Sorts learners into groups by what their records show, for the teacher to confirm, and suggests each group's help level and note `hard-moments` Adds the strategy a teacher chose for when learners struggle, and gathers f",{"id":1281,"title":1282,"text":1283},"how-much-does-it-help","How much does it help?","Early and honest: on 50 learner messages and two models, a one-line tutor prompt got 8% of first drafts past review, and a teacher's\nsettings given before the reply got 68 to 74%. Choosing what goes in for each turn added about 10 more points and a quarter fewer tokens,\nwhich isn't yet clear of chance on that set, and is clear on not giving the answer away (18 to 20 points on an open-weight model). Finding\nthe right misconception for a message worked on only 4 of 19 turns. Rated by a model, on replies, not learning\n([what was done](\u002Fdocs\u002Fharness\u002Fevidence)).",{"id":1285,"title":1286,"text":1287},"it-doesn-t-compete-with-where-its-knowledge-comes-from","It doesn't compete with where its knowledge comes from","[Learning Commons](https:\u002F\u002Flearningcommons.org) and [Eedi](https:\u002F\u002Fwww.eedi.com) keep the shelves: standards, learning components,\nmisconceptions, evaluators. If you want lookups in your assistant, use their MCP server. The Harness decides at each moment what to\ntake from them, credits them, and measures whether it helped. It carries their open data under CC BY 4.0, with the attribution to keep\n([NOTICE](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002FNOTICE)).",{"id":1289,"title":1290,"text":1291},"reference","Reference","[How it works](\u002Fdocs\u002Fharness\u002Fhow-it-works) · [With your stack](\u002Fdocs\u002Fharness\u002Fwith-your-stack) · [MCP](\u002Fdocs\u002Fharness\u002Fmcp) · [Skills](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Ftree\u002Fmain\u002Fharness\u002Fskills\u002F) · [Agent tests](\u002Fdocs\u002Fharness\u002Fagent-tests) ·\n[Components](\u002Fdocs\u002Fharness\u002Fcomponents) · [Open resources](\u002Fdocs\u002Fharness\u002Fresources) · [Catalog](\u002Fdocs\u002Flibrary) · [xAPI](\u002Fdocs\u002Fharness\u002Fxapi) ·\n[Conformance](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fconformance\u002FREADME.md) This page comes from the open repository: [harness\u002FREADME.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002F",{"path":1293,"title":1294,"description":1295,"updated":13,"minutes":1296,"sections":1297},"harness\u002Fhow-it-works","How the Harness works","The Harness is about 10,000 lines of JavaScript with no dependencies, and two compact data files.",7,[1298,1300,1304,1308,1312,1316,1320,1324,1328],{"id":17,"title":17,"text":1299},"The Harness is about 10,000 lines of JavaScript with no dependencies, and two compact data files. It has no server, no\nindex and no model of its own, and it keeps nothing unless you ask. It can be that small because it doesn't search\neverything on every turn: a person narrows things down first, and each turn only chooses from what is left.",{"id":1301,"title":1302,"text":1303},"one-turn","One turn","A learner writes *\"i got 2\u002F5\"*. **What the teacher decided is already a short list:** a goal, how far the AI may go, perhaps a standard, a few\nmisconceptions and some guidance from research. **The moment is read:** giving an answer, asking for the answer, stuck, or trying again. **The list is cut down to this moment:** one teaching move, and at most two of each other kind. **Instructions are written** from the teacher's words and the resources' own text, never the learner's, so what a\nlearner types can change what is chosen but can't write instructions. **Your model replies,** with those instr",{"id":1305,"title":1306,"text":1307},"two-stages-which-is-why-there-is-no-vector-store","Two stages, which is why there is no vector store","Stage Who From To When Choosing for an activity A teacher, or an assistant working with one Every state's standards, 8,117 misconceptions, 77 pieces of guidance and whatever others publish One standard, up to 8 misconceptions, up to 20 pieces of guidance, kept in the activity Once, before class Choosing for a turn Rules, or a small model That short list One move, and at most 2 of each kind Every message What is chosen for an activity is kept as it was, with where it came from. A run never waits on a far-off service, and a\nrecord can say exactly what the AI was given.",{"id":1309,"title":1310,"text":1311},"the-pool","The pool","What How much Where it lives Who narrows it What reaches the model What the teacher decided One activity's settings Your file, or the activity on the Engine or the Developer Platform The teacher The goal and the limit, every turn Teaching moves 4, with their sources In the code, [`src\u002Fbrief.js`](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fsrc\u002Fbrief.js) The moment One Misconceptions ([Eedi](https:\u002F\u002Fwww.eedi.com)) 8,117, under 2,258 skills, 1.2 MB In the package The teacher ticks up to 8, or words search the skills At most 2 Durable skills (Carnegie) 60 KB In the package The ",{"id":1313,"title":1314,"text":1315},"how-a-turn-chooses","How a turn chooses","**By rules, with no model call and no cost.** Patterns read the moment, in English. A list item is taken when it shares at\nleast two words with what was just said, the closest first. Guidance can name its moments, and then those for the moment take\nturns as the conversation goes on, so a learner doesn't hear the same two every time. **By a small model, for one more call a turn** (`pickerModel`). It reads the conversation and the numbered list, and answers\nwhich to take. It reads any language, and more of the moments. If it fails or takes too long, rules decide. How well this works so far: of 3",{"id":1317,"title":1318,"text":1319},"keywords-embeddings-and-rag","Keywords, embeddings and RAG","The Harness does no retrieval. A list of a dozen items needs no index, and everything above it was narrowed by a person. Retrieval is for your own documents, such as a library's guides, and belongs to whatever holds them: Where Keywords By meaning Reranked Where the vectors are The Engine's knowledge BM25 Once a model for it is set in the console No In its own store, made the first time a file is searched, and kept The Developer Platform's knowledge BM25 Yes Yes In a vector index on the platform The Harness No No No None Using both generally finds more than keywords alone: meaning catches a pa",{"id":1321,"title":1322,"text":1323},"skills-mcp-and-the-gateway","Skills, MCP and the gateway","All of them reach the same code. What differs is what runs where, and whether it's guaranteed to run. Way in What it is What runs, and where Where the data is Guarantees it runs [Skills](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Ftree\u002Fmain\u002Fharness\u002Fskills\u002F) Text files an assistant reads Nothing runs: the assistant follows them, or doesn't None No [MCP, local](\u002Fdocs\u002Fharness\u002Fmcp) `viable-harness mcp`, started by your assistant This code, on your computer In the package, and what you fetched No: the assistant chooses to call it [MCP, hosted](\u002Fdocs\u002Fharness\u002Fmcp) `https:\u002F\u002Fapi.viablelab.org\u002Fmcp` Th",{"id":1325,"title":1326,"text":1327},"where-the-engine-and-the-developer-platform-use-it","Where the Engine and the Developer Platform use it","Neither has a copy: they import these same files, so a change here reaches all three. Part of the Harness The Engine The Developer Platform Teaching settings, in `How the AI helps` Yes Yes The turn's brief On by default A choice in How the AI helps, off by default, read by rules VETTING's prompts and rounds Its VETTING step Its VETTING step and `\u002Fv1\u002Fvetting\u002F*` Misconceptions and durable skills Choosing them for an activity Choosing them for an activity Measures Records The research export Suggested changes Progress Progress Formats, agent tests' verdicts Yes Yes MCP tools `viable-engine mcp` T",{"id":1329,"title":1330,"text":1331},"why-it-is-this-small","Why it is this small","**It lends nothing out and keeps nothing:** a host passes in `fetch`, a model and a store. **It has no dependencies,** so what you install is what you see: 0.8 MB packed. **It holds decisions, not knowledge:** the data it carries is small and compact, and what is large stays with its makers\nuntil someone asks for it. This page comes from the open repository: [harness\u002Fdocs\u002Fhow-it-works.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fdocs\u002Fhow-it-works.md). To change it, change it there.",{"path":1333,"title":1334,"description":1335,"updated":13,"minutes":1296,"sections":1336},"harness\u002Ffor-builders","Building for learners","Teachers and researchers now build classroom tools by describing them to a coding agent.",[1337,1339,1343,1347,1351,1355,1359],{"id":17,"title":17,"text":1338},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. Teachers and researchers now build classroom tools by describing them to a coding agent. The agent knows how to write the code.\nWhat it can't see is the room: thirty children opening this at 9:05 from one school address, a shared Chromebook, a quiz whose\nanswers are one View Source away, a tutor that does the homework, a key the class can spend. The rest of the Harness goes around **the AI that teaches**, at each reply. This part goes around **the AI that bu",{"id":1340,"title":1341,"text":1342},"use-it","Use it","**With a coding agent** such as Claude Code, Codex or Cursor: Then ask for what you want as you normally would. It asks who the learners are first, and builds the least that does the job. It\nadds a few lines to the project's `AGENTS.md` too, since agents can skip a Skill they aren't reminded of. **With a chatbot** such as ChatGPT or Gemini, where you can't install anything, paste this at the start: **On a project you already have**, a coding agent's or your own:",{"id":1344,"title":1345,"text":1346},"what-review-looks-for","What review looks for","It reads text with patterns, with no model and nothing leaving the computer, and says where to look and what to do. It exits 1 when\nsomething should be fixed before learners use it. Fix before learners use it Why A secret key written into the code A key goes wherever the code goes A model called from the page itself Anyone in the class can read how, and spend without end A database anyone can read or write Anyone with the page's address can read every learner's work A `.env` file git isn't told to ignore The keys in it go into the repository with the next commit A server handing out its whole ",{"id":1348,"title":1349,"text":1350},"test-its-tutor","Test its tutor","If the tool has an AI that replies to learners, [`tutor-tests.json`](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fskills\u002Fbuilding-for-learners\u002Ftutor-tests.json) has 14 cases\nfor what you'd worry about in a new colleague: asking for the answer outright, again after a hint and with \"my teacher said you can\";\na wrong answer given with confidence, and a right one; being stuck; a learner who may not be safe; a request that isn't; personal\ndetails; \"are you a real person?\"; wanting a secret friend; and an attempt to get at its instructions. Change the question and answer\nto the to",{"id":1352,"title":1353,"text":1354},"with-viable-s-services-or-without","With VIABLE's services, or without","None of this needs an account with us. The Skill points to what fits, with what each leaves to you: You want Use You still do A class with an AI tutor, and no page of your own The [VIABLE Learning Platform](https:\u002F\u002Fwww.viablelearning.org) Nothing to build Your own page, with someone else minding keys, limits and traffic The [VIABLE API](https:\u002F\u002Fdevelopers.viablelab.org\u002Fdocs\u002Fconnect\u002Ffirst-run) as your model endpoint: a key per class, each with its own requests per minute, daily tokens and monthly cost, and usage to look at A small server that holds the key Your own page and your own model accou",{"id":1356,"title":1357,"text":1358},"what-it-can-t-do","What it can't do","**It shapes what gets written, and nothing after that.** In class, someone has to hold the key, watch the budget, read what happened and answer for it. Hosted platforms do that. A Skill can't. **It isn't legal advice, and it can't say a tool complies with anything.** [The notes on learners' data](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fskills\u002Fbuilding-for-learners\u002Freferences\u002Flearner-data.md) link to the sources and list what a school will ask. **We haven't measured whether it helps.** The patterns in `review` come from what we've seen in classrooms and from services' do",{"id":1360,"title":1361,"text":1362},"how-it-differs-from-other-skills-for-teachers","How it differs from other Skills for teachers","Most Skills for education help with a teacher's own work: lesson plans, rubrics, differentiation. They're useful, and this doesn't do\nany of that. Standards and misconception data is well served already, by [Learning Commons](https:\u002F\u002Flearningcommons.org) among others, and this points to it. What\nwe couldn't find elsewhere, when we looked in November 2026, is what this is for: treating a class as the unit of load and budget, saying what not to build, and tests of a tutor you can run on a small tool. This page comes from the open repository: [harness\u002Fdocs\u002Ffor-builders.md](https:\u002F\u002Fgithub.com\u002FVIAB",{"path":1364,"title":1365,"description":1366,"updated":13,"minutes":1367,"sections":1368},"harness\u002Fwith-your-stack","With your stack","You already have an AI app or an agent, in any language or framework. You won't rewrite it. Two ways in:",9,[1369,1371,1375,1379,1383,1387,1391,1395,1399,1403,1407,1410,1414,1418,1422,1426],{"id":17,"title":17,"text":1370},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. **Change one URL.** The gateway stands in front of your model endpoint. Each call gets what its moment\nneeds from a teacher's settings, each reply is checked before your app sees it, and a record is kept for\nthe teacher's page. **Call it yourself.** `prepare` before your model, `check` and `record` after it, and agent tests in CI\n(`viable-harness test --endpoint`). What goes in is chosen for each message: the teacher's goal and limit always, the teaching mov",{"id":1372,"title":1373,"text":1374},"change-one-url","Change one URL","Write the teacher's settings (`fractions.json`). `teaching` is [How the AI helps](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fschemas\u002Fteaching.schema.json); a plain teaching\nsettings file, or a course package, works as well. Start the gateway in front of your endpoint. Name the reviewer: it turns the check on. It says what it will do, then gives you the line to change: Then call it as you call any OpenAI-compatible endpoint. Ran:",{"id":1376,"title":1377,"text":1378},"what-comes-back","What comes back","Your endpoint's answer, with the reply it may have changed, and what the check did in `X-Viable-*`\nheaders and a `viable` field. In a stream the field is on the chunk with the `finish_reason`. Header Field Says `X-Viable-Outcome` `outcome` `passed`, `unverified`, `attention` or `not_checked`. Only `passed` is checked (`checked: true`) `X-Viable-Delivered` `delivered` `reply` (as your model wrote it), `rewrite`, `fallback`, or `tool_calls_only` (failed text was dropped from beside a tool call) `X-Viable-Why` `why` Why it isn't `passed`: `checks_off`, `tool_calls`, `structured_output`, `no_text`",{"id":1380,"title":1381,"text":1382},"streaming","Streaming","With checks on, the reply is held until it passes, then sent as a normal stream, so a failed draft never\nreaches a learner. The first word arrives after the whole reply and its review. With checks off, the\nstream passes through as it comes, and the headers are all that report on it.",{"id":1384,"title":1385,"text":1386},"tool-calls","Tool calls","They go back to your app unchecked, and the text that comes after them is checked. Text beside a tool\ncall is checked too; if it fails, the tool call goes on without it.",{"id":1388,"title":1389,"text":1390},"which-activity-which-learner","Which activity, which learner","Header Does Without it `X-Viable-Activity` Picks the settings from `--activities \u003Cdir>`: one `\u003Cactivity>.json` per activity, and a package's activities by their titles (the gateway lists the names when it starts). Unknown names are refused With `--activities`, the call is refused. With `--teaching`, every call gets those settings `X-Viable-Learner` Names the learner in the records. A pseudonymous ID: letters, digits, dots, dashes, underscores, colons. A name or an email is refused The request's `user` if it fits that rule, else the call is kept with no learner `X-Viable-Section` Applies that s",{"id":1392,"title":1393,"text":1394},"keys-and-tokens","Keys and tokens","You start it with Your app sends The endpoint gets `OPENAI_API_KEY` set (or `--api-key-env NAME`) Any API key: it isn't used The gateway's key That variable not set Its own key The app's key `--token-env NAME` and a key The token as its API key, or as `X-Viable-Token` The gateway's key `--token-env NAME` and no key Its own key, and the token as `X-Viable-Token` The app's key Without a token the gateway answers this computer only: `--host` refuses anything else, and it refuses another\nname in `Host`, a proxy that says it passed the request on, and a post with another site's `Origin`. A token ho",{"id":1396,"title":1397,"text":1398},"the-teacher-s-page","The teacher's page","The gateway deletes nothing by itself: what it keeps in `--data-dir` stays until you delete it, or `viable-harness\nforget` takes one learner's. Open `\u002Fteacher`. Your browser asks for a password: it's the token, or with no token the one the gateway printed\nwhen it started, and any user name will do. It always asks, because through a local proxy every visitor would\nlook like this computer. With several activities it asks which. Or from the folder: With `--activities`, read one activity's at a time: `.\u002Frecords\u002Fgateway\u002F\u003Cactivity>`. The page shows How the AI helps, what the check did with each repl",{"id":1400,"title":1401,"text":1402},"on-any-cloud","On any cloud","`gatewayHandler(options)` from `src\u002Fgateway.js` returns `(Request) => Response` and imports nothing from\nNode. A store is four functions (`get`, `put`, `delete`, `list`). Ran locally in `wrangler dev` 4.148.0\n(not deployed), with a KV namespace bound as `RECORDS`: KV's `list` returns 1,000 keys at a time, so page through it once an activity has more.",{"id":1404,"title":1405,"text":1406},"your-client-or-framework","Your client or framework","Each of these points at the gateway and sends the two headers. Each ran, with a held reply and a rewrite. **OpenAI Python** (`openai` 3.44.0, streaming and tool calls too): **OpenAI Node** (`openai` 7.49.0, streaming too): **LangChain** (`langchain-openai` 1.7.3, `invoke` and `stream`). **LangGraph** (`langgraph` 1.2.19) takes\nthis `llm` in a node as it is: **OpenAI Agents SDK** (`openai-agents` 0.24.0). It uses `\u002Fv1\u002Fresponses` by default, which the gateway\nrefuses, so name the chat completions model. Its tracing sends runs to OpenAI unless you turn it off: **Vercel AI SDK** (`ai` 7.0.162, `@a",{"id":1408,"title":1409,"text":17},"the-parts","The parts",{"id":1411,"title":1412,"text":1413},"a-check-as-a-guardrail","A check as a guardrail","From JavaScript. Ran: From any language, over the gateway. Ran from Python with no dependency: `\u002Fv1\u002Fcheck` takes `reply`, `message`, `history`, `guideline`, `reference`, `question`, `answer_key`,\n`key_concepts`, `explanation`, `instructions`, `teaching`, `section`, `rewrite`, `model` and `reviewer`. The\nreviewer defaults to the gateway's. It keeps no record.",{"id":1415,"title":1416,"text":1417},"instructions-from-how-the-ai-helps","Instructions from How the AI helps","Add them after your reply model's own instructions. Keep `watch_for` for the teacher: never send it to a\nmodel. Ran: In JavaScript: `teachingContext(settings, section)?.text`. Without `teaching`, the route uses the activity\nin `X-Viable-Activity`, or the gateway's only one.",{"id":1419,"title":1420,"text":1421},"misconceptions","Misconceptions","Eedi's Misconceptions Graph, from the harness's own copy: math only, no model, no network beyond the\ngateway. Keep `source.attribution` with anything you copy (CC BY 4.0). Ran: These routes, like everything on the gateway but `\u002Fhealth`, need the token when it has one.",{"id":1423,"title":1424,"text":1425},"agent-tests-in-ci","Agent tests in CI","`viable-harness test` exits non-zero when a case fails or can't run, so the step fails with it. Ran, with\na passing case, a failing one and an endpoint that's down. It runs a VIABLE agent file, so to test your\nprompt, put it in a one-step file (`tutor.agent.json`): Cases (`cases.json`) say what a good reply does; [agent-tests.md](\u002Fdocs\u002Fharness\u002Fagent-tests) has the rest: `--teaching` takes a plain teaching settings file. Point this at your model endpoint, not at the gateway:\nthe judge's own verdicts shouldn't be checked. The workflow below hasn't run on GitHub. Each case costs a reply and a jud",{"id":1427,"title":1428,"text":1429},"what-the-gateway-can-t-do","What the gateway can't do","**It sees only what passes through it.** An app can call the model some other way, and nothing here can\nstop steps the app does elsewhere. **A review is a model's judgment,** not a guarantee that a reply is right or safe. **It checks text to the learner.** Tool call arguments aren't checked. Structured output\n(`response_format` of `json_object` or `json_schema`) passes through as `not_checked`, because a rewrite\nor a fallback would break your code. **The reviewer and rewrites read text only.** Pictures in a message don't reach them, nor do tool results.\nA rewrite gets your system message, the ",{"path":1431,"title":1432,"description":1433,"updated":13,"minutes":35,"sections":1434},"harness\u002Fadd-your-research","Add your research","You have studied what helps learners.",[1435,1437,1441,1445,1449,1453],{"id":17,"title":17,"text":1436},"You have studied what helps learners. This is how a teacher setting up an activity can use it, with your name on it, in whatever AI\nthey use. There is nothing to host, no learner's data comes to you, and no code to write if you'd rather not.",{"id":1438,"title":1439,"text":1440},"what-it-looks-like-once-it-s-in","What it looks like once it's in","A teacher opens **How the AI helps** in an activity and sees your topic beside the others, each piece with its source. They tick the ones\nthat fit. Later a learner writes *\"i got 2\u002F5\"*. Only the pieces that fit that moment reach the model, and they arrive saying where they came from: Three things follow from that: **Your citation travels with your words.** Anyone who reads what the AI was given sees it. **Your work gets a page** in the [library](\u002Fdocs\u002Flibrary), with what you showed, what comparing replies showed, and what nobody has shown yet. **You can see that it's used.** The Developer Plat",{"id":1442,"title":1443,"text":1444},"what-you-write","What you write","One file, in plain sentences. Each piece says which moments it is for: `attempt` (they gave an answer), `asks_answer`, `stuck` or `repeated` (they keep trying). `about` is optional: words that have to come up in the conversation. `evidence` is the part that matters most. It keeps three things apart that are easy to blur: what your study found, what this particular use of it has been shown to do, and what is still unknown. Nobody is asked to claim more than they have.",{"id":1446,"title":1447,"text":1448},"see-what-it-changes","See what it changes","If you have Node installed, this makes the folder, and the second line shows what a model is told with your file and without it: No model is called and nothing is sent anywhere. [Components](\u002Fdocs\u002Fharness\u002Fcomponents) has the details, including other kinds of work: a method that checks a reply, a way to rate replies, a dataset or a benchmark.",{"id":1450,"title":1451,"text":1452},"if-you-don-t-write-code","If you don't write code","[Tell us about your work](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fissues\u002Fnew?template=component.yml) with the sentences and the papers, and we'll write the file with you. You review it before anyone else sees it.",{"id":1454,"title":1455,"text":1456},"how-it-lands","How it lands","It comes as a pull request, from you or from us, with you as its maintainer. We check [seven things](\u002Fdocs\u002Fharness\u002Fcomponents): among them that the source says what the piece says, and that the license lets it travel. It appears in the library. Teachers can then choose it on the Developer Platform and on any Engine. The license stays yours to choose, as long as it lets the words be used: CC BY 4.0 is the usual one. You can correct or withdraw it at any time. A new version goes out with the next release. This page comes from the open repository: [harness\u002Fdocs\u002Fadd-your-research.md](https:\u002F\u002Fgithu",{"path":1458,"title":1459,"description":1460,"updated":13,"minutes":1461,"sections":1462},"harness\u002Fcomponents","Components","You have research: a framework, a list of misconceptions, a classifier, a way to rate replies.",14,[1463,1465,1469,1473,1477,1481,1485,1489,1493,1497,1501,1505,1509,1513],{"id":17,"title":17,"text":1464},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. You have research: a framework, a list of misconceptions, a classifier, a way to rate replies. A component is how it reaches\nthe AIs people are using, with your name on it, without each of them building it in.",{"id":1466,"title":1467,"text":1468},"what-you-have-and-what-you-do","What you have, and what you do","You have Its slot What you write What happens then Guidance, a framework, moves, a list of what goes wrong `context` One JSON file. No server, and no learner's data reaches you Someone adds pieces to an activity, on a screen or from a file, and each turn takes those that fit the moment, with your name beside them A method that writes or checks a reply `step` A manifest, and an address or an MCP tool that runs it A builder puts it in a workflow as a Component step, on the Developer Platform or the Engine A way to rate replies `judge` The same Agent tests rate replies with it, and say so in the ",{"id":1470,"title":1471,"text":1472},"a-context-component-in-five-minutes","A context component in five minutes","That makes a folder from [the template](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Ftree\u002Fmain\u002Fharness\u002Fintegrations\u002F_template-context\u002F). Write your guidance in its `manifest.json`, a\nsentence or two each, saying which moments each is for. Then see what it changes: A teacher or a builder takes the pieces that fit one activity: They go in that activity's settings as they are then, up to 20, with your citation. From there it works wherever those\nsettings go, since they travel with the activity: `prepare`, the gateway, the Engine and the Developer Platform.\n[How the Harness works](\u002Fdocs\u002Fharness\u002Fh",{"id":1474,"title":1475,"text":1476},"what-you-get-for-it","What you get for it","**Teachers' AIs use your work** without anyone rewriting it, on any model. **Your credit travels:** your citation goes with every copy, and a record says which pieces went into a reply. **A way to show what it does:** compare replies with and without it in agent tests, and report that apart from what your\nown studies showed. **A page** with what it does, what it receives, its license, its evidence and how to cite it, once it is reviewed, and how many\nreplies on the Developer Platform it went into in the last 30 days. Servers of one's own report nothing. **Help with the first ones.** VIABLE wri",{"id":1478,"title":1479,"text":1480},"the-six-slots","The six slots","The slot says what the work does in the loop and how its value is checked; without one, a component\nis an entry in a list that nobody can judge. Slot What it does How its value is checked context adds knowledge to How the AI helps, or to what a step sees agent tests with and without it step a method the graph runs agent tests with and without it, and its own measure (answer leakage, for a checker) detector reads turns and work after the turn, and marks something for the teacher agreement with teachers' labels on a sample judge rates replies in agent tests agreement with people's ratings, repor",{"id":1482,"title":1483,"text":1484},"the-manifest","The manifest","Each component has one manifest (`src\u002Fcomponents\u002F` for those in this repository; a JSON file beside\nan outside component's code), checked against `schemas\u002Fcomponent.schema.json` (`viable-harness\nvalidate manifest.json`). Field What `id` `owner\u002Fname`, never reused `version` the component's own version; agents pin it `slot` one of the six above `label`, `summary` what a teacher or builder reads in a list `runs` `core` (JavaScript in this repository, with the export that runs it in `entry` and the step type, if any), `mcp` (the tool's name in `entry`, and in `server` the `command` and `args` that",{"id":1486,"title":1487,"text":1488},"where-a-component-runs","Where a component runs","In the harness's own process only if its code is in this repository, reviewed and tested like the\ncore. Anything else runs out of process, as an MCP server or an HTTP endpoint, and receives only\nwhat `receives` lists; an HTTP one is called only at the hosts in `sends_to`. Work in another\nlanguage (pyBKT, VETTING's Python reference) stays in its own repository and joins out of process;\ntwo implementations of one method share fixtures, which decide when they disagree.",{"id":1490,"title":1491,"text":1492},"calling-one-out-of-process","Calling one out of process","`callComponent(manifest, payload, options)` calls an `http` or `mcp` component with `payload`, an\nobject of the fields `receives` may name, and returns its answer as its slot reads it (below).\n`viable-harness component \u003Cmanifest.json> --input payload.json [--json]` does the same from the\ncommand line, and says which fields it left out. Both are for a manifest you point at yourself. A\ncomponent step in an agent file or a package is someone else's choice, and is\n[held to more](#a-component-in-a-document). **http**: the harness POSTs the payload as JSON to `runs.entry` and reads JSON back. The en",{"id":1494,"title":1495,"text":1496},"the-slots-that-call-one","The slots that call one","Slot Call Sends Expects back judge `runAgentTests(doc, cases, { judgeComponent: manifest })` `case` (its name, rubric, must_not, context and pictures), `reply`, `conversation` (the case's messages) `{ verdict: \"pass\" or \"fail\", reason }` detector `detect(manifest, record)` `learning_events`: the record's `events` and `turns` `{ marks: [{ turn_index, label, evidence }] }`, `turn_index` being the turn's place in the record's `turns`, from 0 measure `measure(manifest, record)` `learning_events`, as above `{ measures: [{ id, value }] }`, each value a number, text, true, false or null context `look",{"id":1498,"title":1499,"text":1500},"a-step-in-a-graph","A step in a graph","A `component` step holds a step component's manifest inline and calls it once per run: **Checked when the graph is**: the manifest fits the schema, its slot is `step`, it runs `http` or\n`mcp` (a host may allow fewer: VIABLE runs `http` only, and a run allows `mcp` only [by name](#a-component-in-a-document)), an HTTP entry is on a host in `sends_to`,\nand `inputs` names only fields the manifest receives. **Sends** each field of `inputs`, a template worked out like any step's setting (a lone\n`{{…}}` keeps its value's type, so `{{history}}` goes as a list). A field `inputs` leaves out isn't\nsent. ",{"id":1502,"title":1503,"text":1504},"a-component-in-a-document","A component in a document","A package or an agent file is a file someone sent you, so opening one must not run their program. A\ncomponent step in a document may use only `http` components, at a public address (HTTPS on port 443, a\nname that doesn't resolve to a private address), by default (`componentRuns: ['http']`). An MCP component starts only when the person running it names its ID: A Node scaffold reads the same list from `VIABLE_ENGINE_ALLOW_COMPONENTS` (comma-separated). The `run_agent`\nand `run_agent_tests` MCP tools never allow one: whoever calls a tool, it isn't the person at the keyboard\nchoosing. A name lifts",{"id":1506,"title":1507,"text":1508},"the-review-checklist","The review checklist","Applied to every component, VIABLE's included: The license of its code, its data and every dependency (code under MIT or Apache-2.0; data\nunder CC BY or more open). Data under a non-commercial license is only ever opt-in context, never\nin a package and never a default. What learner data it receives, and where that goes. What happens when it fails or times out: the step's When something goes wrong path. Attribution, carried into every package that uses it. Fixtures and tests; fixtures are synthetic or licensed for redistribution, never a real learner's\ndata. A named maintainer. A risk level: lo",{"id":1510,"title":1511,"text":1512},"leaving-the-catalog","Leaving the catalog","A component with no answer to a security report in 14 days, or failing its fixtures on two minor\nversions of the harness, is marked unmaintained, and leaves the catalog 60 days later. Agent\nversions that pinned it keep their record.",{"id":1514,"title":1515,"text":1516},"contributing-one","Contributing one","Open an issue first, naming the work, its slot and its license. An outside component goes in\n`integrations\u002F\u003Cowner>-\u003Cname>\u002F`: its manifest, the adapter, fixtures, a README and how to cite it.\nStart from `integrations\u002F_template\u002F`: a detector's manifest, a small HTTP component in plain Node,\nits fixtures, and a README with the steps.\nVIABLE writes the adapter with the authors where it can, the authors review it for fidelity to their\nwork and its limits, and the comparison with and without it fills `evidence.integration`. This page comes from the open repository: [harness\u002Fdocs\u002Fcomponents.md](https",{"path":1518,"title":1519,"description":1520,"updated":13,"minutes":14,"sections":1521},"harness\u002Fresources","Resources: from a public good to your agent","A list of datasets is what the K-12 AI Infrastructure Program's own platform already is.",[1522,1524,1528,1532],{"id":17,"title":17,"text":1523},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. A list of datasets is what the K-12 AI Infrastructure Program's own platform already is. The reason to\ncome here is the next step: a benchmark or a set of rated essays runs against your agent or your judge,\nand changes what you see in a test report.",{"id":1525,"title":1526,"text":1527},"what-connected-means","What connected means","Every entry in the [catalog](\u002Fdocs\u002Flibrary) carries its level in `connect`, so nobody mistakes a listing\nfor a capability. Level Meaning 0. Listed the entry says what it is, its license, and why it goes no further 1. Fetchable `viable-harness resources get \u003Cid>` brings it down, once its license is shown and accepted 2. Connected it has an effect in one command: a test suite, a judge, a calibration set, a knowledge folder, a context lookup or a measure 3. Built in in the core, with no download A copy comes from its makers to `~\u002F.viable-harness\u002Fresources\u002F\u003Cid>\u002F` (or `--dir`), never into this\nrepo",{"id":1529,"title":1530,"text":1531},"one-worked-example","One worked example","A tutor that should give hints, and nothing yet to check that it does: That one command fetched the problems, which keep their numbers, so a later run reads the same cases.\nNow set How the AI helps to hints, and run the same suite: These lines came from a scripted model that shows the mechanism: its tutor gives the answer until it is\ntold how to help. Your model will say what your model does. Before you trust a judge's verdicts, check\nit against people, with no labels of your own: `viable-harness calibrate --set mrbench --judge-model \u003Cmodel>`.",{"id":1533,"title":1534,"text":1535},"write-a-connector","Write a connector","A connector needs no author's cooperation when the license allows the use and each person downloads the\ndata themselves. Ask the authors to review it before you claim anything about their work. Fetch the data from its primary source, and read it. Judge it by what a case made from it would\ntest. If it would test little, say so in the entry and stop: a connector nobody would use is worse\nthan none. Add `src\u002Fresources\u002F\u003Cid>.js`, and list it in `src\u002Fresources\u002Findex.js`: Pure JavaScript with no new dependency: JSON, JSONL and CSV sources, and for a big file `get.lines`,\nwhich filters as it streams. ",{"path":1537,"title":1538,"description":1539,"updated":131,"minutes":35,"sections":1540},"harness\u002Fevidence","What a first check showed","November 2026. A small check, to decide whether the part before the reply is worth building. It is not a study of learning.",[1541],{"id":17,"title":17,"text":1542},"**What was compared**, on each learner message, on two models (one open-weight): Condition What the reply step got Plain a one-line tutor prompt Whole settings that, and everything a teacher set for the activity Brief, by rules that, and what this moment needs, chosen by rules, with no extra call Brief, by a small model the same, chosen by a small model, with one extra call **Cases.** 30 grade 5 fractions messages, written by an author who saw neither the settings nor the conditions, and 20 mistaken\nsolutions from [MathDial](https:\u002F\u002Fgithub.com\u002Feth-nlped\u002Fmathdial)'s test set. A bar for what wou",{"path":1544,"title":869,"description":1545,"updated":13,"minutes":1546,"sections":1547},"harness\u002Fagent-tests","An agent test is a case written from what a teacher or builder wants: the learner's message (or a short conversation), what a good reply does, and what it…",10,[1548,1550,1554,1558,1562,1566,1570,1574,1578,1582],{"id":17,"title":17,"text":1549},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. An agent test is a case written from what a teacher or builder wants: the learner's message (or a\nshort conversation), what a good reply does, and what it must not do. Each case runs through the agent, and a judge\nmodel rates the reply pass or fail with a reason. Your agent is whatever you have: `viable-harness test cases.json\n--endpoint \u003Curl> --endpoint-model \u003Cid> --judge-model \u003Cid>` tests what's behind any OpenAI-compatible endpoint, and\n`viable-engine tes",{"id":1551,"title":1552,"text":1553},"the-case-format","The case format","A file of cases (format `viable.agent-tests`), or a bare list of cases: Field Limit `name` required 120 characters `message`, or `messages` required: the learner's message, or a conversation of `{\"role\": \"user\" | \"assistant\", \"content\": \"...\"}` ending with the learner's 12 messages of 4,000 characters each `rubric` required: what a good reply does 2,000 characters `must_not` what the reply must not do 1,000 characters `context` the run's reference data, such as the open question and its answer key; the judge sees it too 32 KB `pictures` pictures the conversation has already shown, `{\"url\": \"ht",{"id":1555,"title":1556,"text":1557},"writing-good-cases","Writing good cases","Read the activity's teaching settings first (the `teaching_instructions` MCP tool shows what the\nagent is told). Then write a case for each thing a teacher worries about: **The ordinary request**: what most learners will send. The rubric says what a good reply does\nat the activity's help level. **Asking for the answer**: outright, again after a hint (a `messages` conversation), and with\npressure (\"my teacher said you can\"). **A real attempt showing a misconception** the activity listens for, and whether the reply notices\nit without giving the answer. **A section's own help level**: for learner",{"id":1559,"title":1560,"text":1561},"drafting-cases-from-how-the-ai-helps","Drafting cases from How the AI helps","From code, `draftCases(teaching, { model, count, section, baseUrl, apiKey, fetch })`, or with your\nown model call as `callChatModel`; over MCP, the `draft_tests` tool. A package supplies its first\nactivity's teaching settings. The drafting model gets what the reply steps are told (`teachingContext`: the goal, the standard or\nskill, the misconceptions, the help level and, with `--section`, that section's own level and note),\nand is asked for `count` cases (1 to 25, default 8) covering, one case each and in this order:\n`ordinary`, `asks_for_answer`, `misconception` (when the activity lists misco",{"id":1563,"title":1564,"text":1565},"reviewing-drafted-cases","Reviewing drafted cases","Drafts are a model's guesses at what the teacher means. A person reads each one before it is run: Is the learner's message something a learner in this activity would send? Edit it to sound like\nthem. Does the rubric hold the AI to the help level the teacher chose, no more and no less? Is `must_not` precise enough that a judge can tell, from the reply alone? For a misconception case: does the attempt show the misconception, and only that one? For distress and unsafe cases: is the rubric what your school would want a reply to do? Add your\nschool's own instructions, such as whom to tell. Delete c",{"id":1567,"title":1568,"text":1569},"cases-from-past-turns","Cases from past turns","From code, `casesFromTurns(records, teaching, { limit })`. Records are learners' records in the\nlearning events format (`schemas\u002Flearning-events.schema.json`, `{ user, events, turns }`), a record or\na list of them per file. No model is involved, so the same records always give the same cases. From each learner's turns, in\ntime order, it takes: Kind A turn `not_passed` whose reply VETTING didn't pass, when the turn says so (`vetting` other than `passed`, as the research export's turns carry it) `after_wrong_answer` whose learner's latest graded answer since their previous message was wrong: a `",{"id":1571,"title":1572,"text":1573},"learners-words-and-consent","Learners' words and consent","These cases hold what learners wrote. The harness reads records only from the files or arguments\nyou give it, refuses a record whose `user` looks like an email address, and puts learner text only\ninto the cases; but whether you may reuse learners' messages is yours to decide, not the harness's.\nA deployer needs consent to reuse learners' messages as test cases (from learners, and from parents\nfor minors, as your school's or study's terms say), and should: use records with pseudonymous IDs only, never names or emails; read each case before running or sharing it, and remove anything that names a",{"id":1575,"title":1576,"text":1577},"the-judge","The judge","`runAgentTests` (or the `run_agent_tests` MCP tool) runs each case through the agent, then sends the\njudge model the case's reference data, the pictures already shown, the conversation, the reply, what\ncame with it, the rubric and `must_not`, and asks for `{\"verdict\": \"pass\" | \"fail\", \"reason\": \"...\"}`\n(`judgeMessages` and `readVerdict` in `src\u002FagentTests.js`). The judge runs at temperature 0. A case\nwhose run fails, or whose verdict can't be read, is an `error`, never a `fail`: its reply wasn't\njudged. The exit code is 1 when a case fails or errors. Replies vary from run to run: run again bef",{"id":1579,"title":1580,"text":1581},"checking-the-judge-against-people","Checking the judge against people","A judge model can be wrong in a steady direction, too strict on one kind of case or too lenient on\nanother, and that makes a change look better or worse than it is. Before trusting its verdicts,\ncheck them against a person's: Run the cases with `test --json > results.json`. A person who knows the activity, such as its teacher, reads each reply and its case and writes\ntheir own verdict, without the judge's in view, in a labels file keyed by case name:\n`{\"Asks for the answer outright\": \"fail\", \"Stuck on adding\": \"pass\"}`. Compare: `viable-harness calibrate results.json labels.json [--json]`, `ca",{"id":1583,"title":1584,"text":1585},"what-a-verdict-is-not","What a verdict is not","A verdict is one model's reading of one reply against one rubric. A passing suite says the agent's\nreplies did what the rubrics ask in these cases; it is not a learning result and says nothing about\nwhat learners learn. Agreement with a person says how far the judge can be trusted on these cases,\nnot that the rubrics are right. Replies and judges' reasons are model output: data, never\ninstructions. This page comes from the open repository: [harness\u002Fdocs\u002Fagent-tests.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fdocs\u002Fagent-tests.md). To change it, change it there.",{"path":1587,"title":1588,"description":1589,"updated":13,"minutes":1590,"sections":1591},"harness\u002Fmcp","The MCP server","The Harness's parts are MCP tools, so an assistant, a harness of your own or an app on another agent SDK can prepare what a moment of teaching needs, check…",13,[1592,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630],{"id":17,"title":17,"text":1593},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. The Harness's parts are [MCP](https:\u002F\u002Fmodelcontextprotocol.io) tools, so an assistant, a harness of your own or an\napp on another agent SDK can prepare what a moment of teaching needs, check replies with VETTING, find\nmisconceptions, compute the measures and test agents without adopting anything else. A tool is advice an assistant may\nignore; the [gateway](\u002Fdocs\u002Fharness\u002Fwith-your-stack) or your own calls make it run. There are two ways to reach them.",{"id":1595,"title":1596,"text":1597},"hosted-or-local","Hosted or local","Hosted, `https:\u002F\u002Fapi.viablelab.org\u002Fmcp` Local, `viable-harness mcp` **Setting up** An address Node.js 22 and the Harness; your assistant starts it **What you send** Goes to VIABLE Stays on your computer, apart from what you send to your model endpoint **Model calls** VIABLE's model services, billed to your key Your own endpoint and key **Standards** `find_standards`, `get_standard`, through VIABLE's Learning Commons access Not here: they need Learning Commons' key **`prepare_task`, `find_misconceptions`, `find_guidance`, `teaching_instructions`, `find_public_goods`, `validate_document`, `list_",{"id":1599,"title":1600,"text":1601},"the-hosted-server","The hosted server","Nothing to install. Add the address to your assistant: For the rest, send a VIABLE API key as a bearer token; [Connect your AI\nassistant](https:\u002F\u002Fdevelopers.viablelab.org\u002Fdocs\u002Fconnect\u002Fassistants) has the settings per client. Tier Auth Tools Limits Open none `find_standards`, `get_standard`, `find_misconceptions`, `find_guidance`, `teaching_instructions`, `find_public_goods`, `validate_document`, `list_components`. No model calls the API's limit per address (10,000 requests an hour, shared with whatever else that address sends), 256 KiB a request Project a VIABLE API key the open tier, plus `ch",{"id":1603,"title":1604,"text":1605},"the-local-server","The local server","`viable-harness mcp` speaks stdio: newline-delimited JSON-RPC 2.0, protocol version 2025-06-18 (or\n2025-03-26 and 2024-11-05, when a client asks for one of those). stdout carries only protocol messages;\nlogs go to stderr.",{"id":1607,"title":1608,"text":1609},"the-tools","The tools","Tool What it does Calls a model `check_reply` Reviews one reply with VETTING against a guideline, or a question's answer key, and the activity's teaching settings; with `rewrite`, rewrites a failed reply and reviews it again. A reply is checked only when `outcome` is `passed`. yes: `reviewer` (else `model`), and `model` for rewrites `prepare_task` What a model should have in mind before a piece of teaching work: for `tutoring`, from the teacher's settings and the learner's message, the moment, its move and only what the message calls for; `adapt-plan`, `differentiate`, `hard-moments` and `goal",{"id":1611,"title":1612,"text":1613},"where-the-local-server-runs","Where the local server runs","On your own computer. You don't run it yourself: your assistant starts `viable-harness mcp` as a child\nprocess when it starts, talks to it over that process's stdin and stdout, and stops it when it quits. That's why setting it up means telling the assistant the\ncommand, not an address. Tools that call a model reach the endpoint in the server's environment\n(`OPENAI_API_KEY`, `OPENAI_BASE_URL`); everything else (validating a file, writing How the AI helps,\nfinding misconceptions, the measures, the catalog, the components) needs no key and nothing from\nthe network. Tried for real on 2026-11-03 in",{"id":1615,"title":1616,"text":1617},"configuring-a-client-for-the-local-server","Configuring a client for the local server","Every client needs the same three things: the command `node`, the arguments\n`[\"\u002Fpath\u002Fto\u002Fviable\u002Fharness\u002Fbin\u002Fviable-harness.mjs\", \"mcp\"]` (or the command `viable-harness` and\n`[\"mcp\"]` after `npm link`), and the environment for model calls. Claude Desktop (`claude_desktop_config.json`) and Cursor (`.cursor\u002Fmcp.json`): VS Code (`.vscode\u002Fmcp.json`): Codex CLI: Claude Code: With an open-weight model, set `OPENAI_BASE_URL` to its endpoint (for example\n`http:\u002F\u002Flocalhost:11434\u002Fv1` for Ollama) and name its models in each call. An agent SDK with an MCP\nclient starts the same command over stdio. A host t",{"id":1619,"title":1620,"text":1621},"views-in-assistants-that-show-mcp-apps","Views in assistants that show MCP Apps","In a host that renders [MCP Apps](https:\u002F\u002Fmodelcontextprotocol.io\u002Fextensions\u002Fapps\u002Foverview) (the\n`io.modelcontextprotocol\u002Fui` extension, [specification\n2026-01-26](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Fext-apps\u002Fblob\u002Fmain\u002Fspecification\u002F2026-01-26\u002Fapps.mdx)),\nthree tools' results also show as a small page in the conversation: Tool View What it shows `check_reply` `ui:\u002F\u002Fviable-harness\u002Fcheck-reply` The outcome and whether the reply is checked; the learner's message and the guideline, or the question (never its answer key), it was checked against; each review round with its verdict and the review",{"id":1623,"title":1624,"text":1625},"limits","Limits","A call's arguments are at most 1 MB as JSON; longer texts have their own limits in each tool's\nschema (a reply 20,000 characters, a message 8,000, a conversation 50 messages), and agent tests\ntake 1 to 25 cases. A call over a limit gets an error result naming it.",{"id":1627,"title":1628,"text":1629},"what-the-server-guarantees-and-what-it-doesn-t","What the server guarantees, and what it doesn't","**The host decides whether a check runs.** Nothing makes an assistant call `check_reply` before\nshowing a reply, and the server never sees a reply it isn't given. A reply counts as checked only\nwhen `outcome` is `passed`; `unverified` and `attention` replies are not. **The host decides what is kept.** The server reads and writes no files for a call and keeps\nnothing between calls: documents and records arrive as arguments, and results go back to the host,\nwhich keeps or drops them. **Only the reference harness guarantees that VETTING runs and that the trace is kept.** An agent run\nby the harne",{"id":1631,"title":1632,"text":1633},"the-protocol","The protocol","`initialize`, `notifications\u002Finitialized`, `ping`, `tools\u002Flist` and `tools\u002Fcall`; for a client that\nrenders views, also `resources\u002Flist`, `resources\u002Ftemplates\u002Flist` (empty) and `resources\u002Fread` (an\nunknown view gets `-32002`). Other methods get `-32601`; a malformed message `-32700` or `-32600`; an\nunknown tool or arguments that aren't an object `-32602`. Arguments that don't fit a tool's schema, or a tool that fails, come back as a\nresult with `isError: true` and a message saying what to change, so the assistant can act on it.\nBatches and cancellation aren't supported; each message is handled",{"path":1635,"title":1636,"description":1637,"updated":13,"minutes":1296,"sections":1638},"harness\u002Fxapi","The xAPI profile for learning events","Schools' learning record stores (LRSs) read xAPI.",[1639,1641,1645,1649,1653,1657,1661,1665],{"id":17,"title":17,"text":1640},"Commands here say `viable-harness`. Until it is on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in its place: it takes every command. Schools' learning record stores (LRSs) read xAPI. `toStatements(record, { homePage, activityBase,\nincludeText })` in `src\u002Fxapi.js`, and `viable-harness xapi`, turn one learner's record\n(`schemas\u002Flearning-events.schema.json`: its `events` and `turns`) into xAPI 1.0.3 statements. The\nharness builds the statements and sends nothing: a deployer posts them to their LRS. `conformance\u002Fxapi.json` holds the cases another implementation of this profile checks itself a",{"id":1642,"title":1643,"text":1644},"a-statement","A statement","Property What it holds `id` A UUID v5 under the namespace `85904702-c63a-51b6-80a6-6d17f20e12a7` (itself the UUID v5 of `https:\u002F\u002Fdevelopers.viablelab.org\u002Fxapi` in the URL namespace). The name is the JSON, keys sorted and no spaces, of `[homePage, user, activity, session, { event }]`; for a turn `{ turn }`, and for an answer its agent graded `{ turn, assessment: \u003Cits index> }`. Missing parts are `null`. `actor` `{ objectType: \"Agent\", account: { homePage, name } }`: an account at `homePage` named by the record's `user`, its pseudonymous id. Nothing else names the learner. `verb` An ADL verb (`h",{"id":1646,"title":1647,"text":1648},"activities","Activities","Activity IRIs are built from the ids the record carries, each part percent-encoded. With\n`act` = `\u003CactivityBase>\u002F\u003Cactivity>` (or `activityBase` alone when the record names no activity): Object IRI Type The activity `act` `https:\u002F\u002Fdevelopers.viablelab.org\u002Fxapi\u002Factivity-types\u002Factivity` A panel `act\u002Fpanels\u002F\u003Cpanel>` (`app` when the event names none) `…\u002Factivity-types\u002Fpanel` A plan or draft `act\u002Fpanels\u002F\u003Cpanel>\u002F\u003Cfield>` `…\u002Factivity-types\u002Fwriting` A rubric `act\u002Fpanels\u002F\u003Cpanel>\u002Frubric` `…\u002Factivity-types\u002Frubric` A rubric criterion `act\u002Fpanels\u002F\u003Cpanel>\u002Frubric\u002Fcriteria\u002F\u003Ccriterion>` `…\u002Factivity-types\u002Frubric",{"id":1650,"title":1651,"text":1652},"verbs","Verbs","Event Verb Object Result `panel.view` experienced (ADL) the panel `writing.start` attempted (ADL) the plan or draft `writing.progress` progressed (ADL) the plan or draft `writing.snapshot` saved the plan or draft `writing.rubric` experienced (ADL) the rubric `writing.rubric_check` checked, or unchecked when `checked` is false the criterion `writing.submit` completed (ADL) the panel `completion: true` `embed.event` the page's own `verb`: ADL's when ADL has it (attempted, answered, completed, passed, failed, progressed, interacted, ...), otherwise VIABLE's; interacted (ADL) without one the page'",{"id":1654,"title":1655,"text":1656},"extensions","Extensions","Every extension is named after the field it comes from, under\n`https:\u002F\u002Fdevelopers.viablelab.org\u002Fxapi\u002Fextensions\u002F` (`…\u002Fextensions\u002Fvisible_ms`). **Result**: an event's `data`, except what the statement carries elsewhere (`verb`, `object`,\n`field`, `correct`, `success`, `completion`, `score`, `max_score`, `duration_ms`, `response`): its\nnumbers and true\u002Ffalse, strings that name something (`set`, `skill`, `item_type`, `color`,\n`content_type`, `asset_id`, `source`), and nested objects the same way. For a turn,\n`first_output_ms`, `signals` and `after`; for an answer its agent graded, `skill`. **Cont",{"id":1658,"title":1659,"text":1660},"what-is-never-sent","What is never sent","A name, an email address or any identifier of the learner but the record's `user`. A `user` that\nlooks like an email address is refused, and so is a record without one. Without `includeText` (the default): what the learner wrote (a snapshot's `text`, an answer's\n`response`, an embedded page's `response`), a question's `prompt`, a turn's `message` and the AI's\n`reply`, and any other string in data, such as an app's own fields: the harness can't tell a label\nfrom something a learner typed. Lists inside data. The skipped event types above.",{"id":1662,"title":1663,"text":1664},"what-includetext-adds","What includeText adds","A deployer opts in, for an LRS that may hold learners' writing. With `includeText: true`\n(`--include-text`), an answer's or embedded page's `response` becomes the result's `response`, and\nevery other string, a snapshot's `text`, a question's `prompt`, a turn's `message` and `reply` and\nan app's own strings, becomes a result extension named after its field. Ids don't change.",{"id":1666,"title":1667,"text":1668},"sending-statements-to-an-lrs","Sending statements to an LRS","The deployer posts the statements; the harness makes no network call. `homePage` names the system whose ids the learners' `user` values are: keep it the same across\nexports (`https:\u002F\u002Fschool.example.org` and `https:\u002F\u002Fschool.example.org\u002F` are different accounts). Exporting a record again gives the same ids, and an LRS doesn't store a statement it already has, so\nre-sending doesn't duplicate. A statement whose content changed under the same id (after switching\n`includeText`, or changing `activityBase`) is refused, often with 409 Conflict: xAPI statements\ncan't be changed, only voided. LRSs limit ",{"id":1670,"title":1671,"lead":1672,"pages":1673},"engine","Engine","AI tutors, courses and class lists on your own server, set up in the browser.",[1674,1706,1822,1848,1902,1939,1977],{"path":1675,"title":1676,"description":1672,"updated":13,"minutes":835,"sections":1677},"engine\u002Foverview","VIABLE Engine",[1678,1680,1684,1688,1692,1696,1698,1702],{"id":17,"title":17,"text":1679},"For a school, a library or a lab that wants this running on its own machines, on any model. Nothing here needs VIABLE's hosted\nservice, and nothing is held back because it is open: add a model and what it makes possible turns on. ![The Engine's console. A teacher writes the goal of an activity and how far the AI may go, types what a learner might say, and sees what the AI would be told at that moment. Then a learner's page opens with that activity, and the AI replies by asking how they got their answer.](https:\u002F\u002Fraw.githubusercontent.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fmain\u002Fdocs\u002Fimages\u002Fconsole.gif)",{"id":1681,"title":1682,"text":1683},"ten-minutes-from-nothing-to-a-class","Ten minutes, from nothing to a class","It keeps everything in `~\u002F.viable-engine`, and opens its console, signed in. **Models.** Add a provider (OpenAI, OpenRouter, Ollama, LM Studio, or any OpenAI-compatible address), test it, and say what each\nof its models is for. **Courses.** Start from nothing, from an example, or from a file. **An activity.** Say what learners do, the goal, and how far the AI may go. Type what a learner might say to see exactly what the\nAI would be told at that moment. **Try it**, as a learner, without being counted. Then open it. **People.** Let anyone with the link in, or paste your class list and print a ca",{"id":1685,"title":1686,"text":1687},"what-each-model-turns-on","What each model turns on","Role Calls What it makes possible Writes replies `\u002Fchat\u002Fcompletions` Replies, word by word. Photos and PDFs from learners, if you say this model reads them Reviews replies `\u002Fchat\u002Fcompletions` A second model in an agent's check step Sets up classes *(optional)* `\u002Fchat\u002Fcompletions`, with tools Ask. Otherwise it uses whichever writes replies Reads the moment *(optional)* `\u002Fchat\u002Fcompletions` A small model picks what each turn needs. Otherwise rules do, in English Hears learners `\u002Faudio\u002Ftranscriptions` The microphone: a voice message, dictation, a conversation by voice Reads aloud `\u002Faudio\u002Fspeech` R",{"id":1689,"title":1690,"text":1691},"a-chat-button-on-your-website","A chat button on your website","A library wants research help on its guides' pages, answering from its own guides, on its own server. **Knowledge.** Make a knowledge base, add the guides as files or read them from your website. **Chat pages.** Make a page: its instructions, its knowledge base, how many messages a visitor gets. Copy the one line onto your site, and say which sites may show it. Anyone who can open it can use it on your model keys, up to the limits you set.",{"id":1693,"title":1694,"text":1695},"from-the-developer-platform","From the Developer Platform","On the Developer Platform, download the course or agent as a package with its files (Courses → ⋯ → Export). In the console:\n**Courses → From a file**. Or start with it: Travels Stays behind Activities, question sets, How the AI helps, agents and their look, chat page settings, knowledge files, website snapshots Learners and their work, class lists, keys, study data A workflow built on the visual editor runs here as it is: you can't redraw it in the console, and you can change its look and page.\nThe other way works too: **Settings → Download**, then import it on the Developer Platform, where mo",{"id":477,"title":478,"text":1697},"`viable-engine deploy \u003Ctarget> [package] --out \u003Cfolder>` writes a folder with its own README and exact commands, and you deploy it\nyourself. With no package, the server starts empty and you set it up at `\u002Fconsole`. A course changed there stays in the store. Target Keeps things in How far it has been tried `node` a folder run by the tests `docker` a volume run under Docker `cloudflare` D1 run with `wrangler dev --local` `firebase` Firestore run in the Functions emulator; never deployed `aws-lambda` an S3 bucket its handler tested; never deployed `cloud-run` a Cloud Storage bucket as a folder it",{"id":1699,"title":1700,"text":1701},"what-it-keeps-and-who-sees-it","What it keeps, and who sees it","Everything stays in your store. A learner's name lives only on the class list, and is never sent to a model. Learner records carry a\npseudonymous code. `--keep-days \u003Cn>` deletes what learners wrote after that many days, and\n`viable-engine forget \u003Ccode>` deletes one learner. [deploying.md](\u002Fdocs\u002Fengine\u002Fdeploying) lists what the code guarantees, and what is\nstill yours to do, such as your agreement with each model provider.",{"id":1703,"title":1704,"text":1705},"more-than-the-console","More than the console","Apps call the same API as the hosted platform (`POST \u002Fv1\u002Fagents\u002F\u003Cslug>\u002Fruns`), so they move by changing an address.\n[self-hosting.md](\u002Fdocs\u002Fengine\u002Fself-hosting) is the reference: flags, the API, what differs from VIABLE. Others:\n[learner-pages.md](\u002Fdocs\u002Fengine\u002Flearner-pages), [deploying.md](\u002Fdocs\u002Fengine\u002Fdeploying), [try.md](\u002Fdocs\u002Fengine\u002Ftry). **Not here:** the visual workflow editor, studies with consent and the research export stay on the\n[Developer Platform](https:\u002F\u002Fdevelopers.viablelab.org). LTI isn't built. The Engine is built on the\n[VIABLE Harness](\u002Fdocs\u002Fharness\u002Foverview), which you can ",{"path":1707,"title":1708,"description":1709,"updated":131,"minutes":1710,"sections":1711},"engine\u002Fself-hosting","Running VIABLE agents on your own server or cloud","Build an agent or a course on VIABLE's hosted platform, then run it somewhere you control, with your own model keys: Cloudflare, AWS, Google Cloud,…",32,[1712,1714,1718,1722,1726,1730,1734,1738,1742,1746,1750,1754,1758,1762,1766,1770,1774,1778,1782,1786,1790,1794,1798,1802,1806,1810,1814,1818],{"id":17,"title":17,"text":1713},"Build an agent or a course on VIABLE's hosted platform, then run it somewhere you control, with your\nown model keys: Cloudflare, AWS, Google Cloud, Firebase, a container, or one machine. It answers your apps\nthe way VIABLE answers them: the same address shape, the same request fields, the same results and\nerrors. This page says how, and what changes. Read [deploying.md](\u002Fdocs\u002Fengine\u002Fdeploying) too before learners use it: it covers consent, privacy laws, data\nagreements with model providers, and retention. **Have a coding AI?** Give it the package and\n[the deploying Skill](https:\u002F\u002Fgithub.com\u002FVI",{"id":1715,"title":1716,"text":1717},"the-whole-path","The whole path","`inspect`, `deploy` and `verify` all take `--json`, and exit 0 when done, 1 when something failed, and 2\nwhen the command was given wrongly.",{"id":1719,"title":1720,"text":1721},"where-it-can-run","Where it can run","`node` `docker` `cloudflare` `aws-lambda` `cloud-run` `firebase` **Runs on** your machine your containers Workers a function URL a service Functions, 2nd gen **Keeps conversations in** a folder a volume D1 S3 a bucket mounted as a folder Firestore **Streams replies** yes yes yes yes, on the function URL yes yes **MCP components, once you name them** yes if its command is in the image no no if its command is in the image no **Longest run** 300 s here; yours to set 300 s here; yours to set 300 s here; no limit of its own while the caller stays connected 280 s here; 900 s at most 280 s here; 3600",{"id":1723,"title":1724,"text":1725},"1-download-the-package","1. Download the package","**A course, with its files.** This is the shortest path, and it keeps the course's knowledge. On VIABLE,\nopen **Teach → Courses** and the course. Press **Download as package**, then **With its files (.zip)**.\nOnly the project's owners and editors can. You get `\u003Ccourse>.viable.zip`. **An agent on its own, with its knowledge.** For a chat page with no course. On **Agents**, open the\nagent's **⋯** menu, choose **Export…**, then **With its knowledge (.zip)**. It is the same kind of\npackage with no course in it, holding the agents and everything below that isn't about a course. The zip holds `packa",{"id":1727,"title":1728,"text":1729},"2-see-what-it-needs","2. See what it needs","It lists the agents and their addresses, every model by role (reply, reviewer, helper, speech, image,\ntranscribe), what those models turn on and why anything is off, the knowledge bases (files, text copies, websites,\nhow VIABLE searched each), the activities, which steps can't run where, the settings it needs, warnings, and the next command.\n`--json` gives the same exactly. `--map` and the other model flags show what they would change.",{"id":1731,"title":1732,"text":1733},"3-replace-the-models","3. Replace the models","The Engine calls an OpenAI-compatible endpoint: OpenAI itself, or an open-weight model behind vLLM,\nOllama, llama.cpp or LM Studio. Agents name VIABLE's models, such as `gpt-oss-120b`, or VIABLE's aliases,\nsuch as `viable-3`. Your endpoint may not know them. Flag What it replaces `--map a=b` (repeat it) The model `a`, in every role, with `b`. `--reviewer-model \u003Cid>` Every reviewer, whatever `--map` says. `--model \u003Cid>` Every chat model not in `--map`, and the reviewer too unless something else names it. `--speech-model`, `--image-model` Every Speech or Image step's. `--models models.json` Any ",{"id":1735,"title":1736,"text":1737},"4-scaffold-and-deploy","4. Scaffold, and deploy","This writes a folder and sends nothing anywhere. It holds a short entry file, `README.md` with the exact\ncommands, `models.json`, your package as one zip in `package\u002F`, and under `vendor\u002F` the Engine's and the\nHarness's code it runs. It refuses a folder that isn't empty, unless you add `--force`. Keep the folder as private as the course: the zip may hold what you uploaded to VIABLE. **Secrets, everywhere.** `VIABLE_ENGINE_TOKEN` is what callers send as `Authorization: Bearer`; make one\nwith `openssl rand -hex 32`. A deployed server refuses agent calls until it is set, and `\u002Fhealth` still\nanswe",{"id":1739,"title":1740,"text":1741},"node-on-one-machine","Node on one machine","It keeps what it keeps in `.\u002Fdata`, and listens on `127.0.0.1:8080`. It speaks plain HTTP, so put a web\nserver with a certificate in front of it (Caddy gets one for you).",{"id":1743,"title":1744,"text":1745},"a-container","A container","Or `docker compose up -d --build` in the folder, with the settings in your shell or a `.env` beside its `compose.yaml`. The package is in the image, about 20 MB on top of `node:22-slim`'s 350 MB. It runs as a user without root, keeps what\nit keeps on the volume, and tells Docker its health. **Stopping.** `docker stop` lets a reply under way finish and be kept, for 10 seconds; `-t 300` waits for the longest\nrun. If the kill comes first, that reply is lost, and what was kept before it stays whole. **Starting again.** `docker rm`, then the same `docker run` on the same volume, goes on with every ",{"id":1747,"title":1748,"text":1749},"cloudflare-workers","Cloudflare Workers","`npx wrangler dev --local` runs it on your machine first, with a local database, touching nothing on\nyour account. The package is a private asset: `run_worker_first` means only the Worker can read it. A daily\ncron deletes what `VIABLE_ENGINE_KEEP_DAYS` has passed, as a daily function does on Lambda and Firebase. Use Workers' paid plan. Reading a package and searching it can take more than the free plan's 10 ms of\nCPU per request; time waiting on your model doesn't count, so the paid plan's 30 s is plenty. A package is\nat most 25 MiB. D1 is\nthe store, not KV, because KV's writes take up to a mi",{"id":1751,"title":1752,"text":1753},"aws-lambda","AWS Lambda","A function URL answers, streaming; the template also makes a private S3 bucket whose lifecycle rules\ndelete old conversations and runs. The store is S3, not DynamoDB, because a DynamoDB item stops at 400\nKB, which a long conversation can pass. The key and the token become the function's environment\nvariables: use Secrets Manager if anyone who can read the function shouldn't read them. Behind API Gateway,\nuse `lambdaHandler` from `server\u002Flambda.js` instead: it holds the answer until it is whole.",{"id":1755,"title":1756,"text":1757},"google-cloud-run","Google Cloud Run","Its README creates the secrets in Secret Manager, the bucket, and the service with `gcloud run deploy\n--source .`, with the bucket mounted at `\u002Fdata`. `--allow-unauthenticated` lets the server be reached; the\ntoken still decides who may run an agent. Set `--max-instances 1` if a lost message in one conversation\nmatters more than scale.",{"id":1759,"title":1760,"text":1761},"firebase","Firebase","`firebase emulators:start` runs it on your machine first; its README has the commands. Functions need the\nBlaze plan. Firestore holds what it keeps, and its rules allow no browser in. A request\nalone never sweeps, so to delete old conversations, set a Firestore TTL policy or call `sweep` from a\nscheduled function. App Hosting is built for web frameworks, and this isn't one. If you want it anyway, it needs what any\nNode server does: a root `package.json` whose `start` script listens on `$PORT` (`deploy node` gives you\nthat), the secrets in `apphosting.yaml` in Cloud Secret Manager, and a store ",{"id":1763,"title":1764,"text":1765},"5-check-it","5. Check it","It checks from outside that the server answers, refuses a caller without the token, takes yours, lists its\nagents, says which package and date it runs, and opens its chat page. With the token it also says what is on and off, and\nwhy. Only `--turn` runs a real turn. It prints what to do about anything that failed. Where the server has a chat page, it also prints the line for your site and the origins your site's\nContent-Security-Policy must allow:",{"id":1767,"title":1768,"text":1769},"6-call-it-from-your-app","6. Call it from your app","Send the request you send to VIABLE. Change the address and the key. The answer is VIABLE's: `object: \"agent.run.result\"`, `conversation_id`, `run_id`, `output` (with `text`, and\n`citations` when the reply cites knowledge), `usage`, `cost` and, with `include`, `trace`. An error is\nVIABLE's `error` object with `message`, `type`, `code` and `request_id`. Branch on `code`. Route What it does `POST \u002Fv1\u002Fagents\u002F{slug}\u002Fruns` Runs one turn, as on VIABLE. `GET \u002Fv1\u002Fagents\u002F{slug}\u002Fruns\u002F{run_id}` Reads a run back, with its trace, as on VIABLE. With a store it lasts until it is deleted; without one the serv",{"id":1771,"title":1772,"text":1773},"without-a-scaffold-viable-engine-serve","Without a scaffold: viable-engine serve","For one machine, with no folder to keep: Option What it does Default `--port`, `--host` Where it listens. `8080`, `127.0.0.1` `--token-env \u003CNAME>` The environment variable that holds the token callers must send. Without it, the server [answers only this computer](#with-no-token). none `--cors \u003Corigin>` The one web page origin allowed to call it from a browser. Without it, browsers can't. none `--max-body \u003Cbytes>` The largest request body. A page that takes recordings or attachments takes up to 14 MB on its message routes, whatever this says. 1048576 (1 MB) `--timeout \u003Cseconds>` The longest a r",{"id":1775,"title":1776,"text":1777},"models-set-up-in-the-browser","Models, set up in the browser","`\u002Fconsole` holds the Engine's own pages for whoever runs it: courses, with [who is in each and how it is going](\u002Fdocs\u002Fengine\u002Flearner-pages#who-a-learner-is), chat pages, agents, knowledge, and Models. On Models, add a provider (OpenAI, OpenRouter,\nOllama, LM Studio, or any other OpenAI-compatible address), test it, and say what each model is for. A feature turns on\nwhen its model is set, and changing one applies to the next message, with no restart. Different roles may be on different\nproviders, each with its own key. Role Calls What it turns on Writes replies `\u002Fchat\u002Fcompletions` Replies, word",{"id":1779,"title":1780,"text":1781},"agents-chat-pages-and-knowledge-in-the-browser","Agents, chat pages and knowledge, in the browser","The console makes what a server runs, with no file to write. Say a library wants a research-help button on its guides: **Knowledge → New knowledge base**, then add its guides as files, or its site by address. **Try a search** runs the real\nsearch and shows the passages it finds. **Chat pages → New chat page**: a title, what it should do, what it answers from. That makes an agent with a page. **Share it**: the page's link and QR code, and the one line that puts a chat button on a website. Each page says what it does as it goes; what follows is what it won't tell you. **Where things live.** An a",{"id":1783,"title":1784,"text":1785},"behind-a-proxy","Behind a proxy","A proxy that terminates https in front of the server is the usual way to put it on a network. Then every visitor's address is the\nproxy's, and limits per address count them all as one: start with `--trust-proxy` (`VIABLE_ENGINE_TRUST_PROXY=1` on a scaffold) so\nthey count the last `X-Forwarded-For` entry instead. Use it only where the proxy is the only way in, since anyone reaching the server\ndirectly could say anything in that header. Have the proxy send the real `Host`, too: the links the console shows come from it.\n`viable-engine start` on `127.0.0.1` with a kept token answers only to this c",{"id":1787,"title":1788,"text":1789},"with-no-token","With no token","`serve` and `gateway` with no token answer this computer only, on every route: A name that isn't `localhost`, `127.0.0.1` or `[::1]`, a connection from elsewhere, and a request a proxy\nmarks as passed on (`X-Forwarded-For`, `Forwarded` and the like) get 403, so a website that points its own\nname at this address learns nothing. A post with another site's `Origin` gets 403, or the one `--cors` names. A program on this computer sends no\n`Origin`, and goes on working. The teacher's page and the console still take a token: the server makes one at start and prints it once. Through a reverse\nproxy th",{"id":1791,"title":1792,"text":1793},"what-a-package-may-do","What a package may do","A package is a file someone sent you. Treat opening one like opening any file from them, and read what\n`viable-engine inspect` says first: each component step and Fetch URL step it holds, and what it would contact\nor start. It can't start a program unless you name it ([components](\u002Fdocs\u002Fharness\u002Fcomponents#a-component-in-a-document)). It can't reach your own network. On every host, a Fetch URL step, a package's file named by a URL, and an\nhttp component go only to a public address. A Worker's transport refuses private ones when it connects. On\na Node host the Engine refuses a name if any answer",{"id":1795,"title":1796,"text":1797},"logs","Logs","Each run writes one JSON line to standard output: the time, the request ID, the agent, the run ID, the\nstatus, the error code if any, the duration, the model calls and the tokens. It never holds what the\nlearner wrote or their ID.",{"id":1799,"title":1800,"text":1801},"conversations","Conversations","With a store (`--data-dir`, or a scaffold's), the runs route keeps conversations as VIABLE does: a turn\nwithout `conversation_id` starts one, its answer has the ID, and a turn with it continues it, reading its\nlatest 100 messages (400 for an agent that summarizes). Ask steps wait for the next message, State lasts\nbetween turns, and an Input step's `\"summarize\"` works. `messages` with two or more entries starts a\nconversation with them. `history_mode: \"client\"` or `\"none\"` keeps no conversation for that turn; the run itself, with its reply and\ntrace, is still kept until `--keep-days` removes it",{"id":1803,"title":1804,"text":1805},"memory-and-forgetting-a-learner","Memory, and forgetting a learner","With a store or `--memory-dir`, a run's `user` is the learner for Memory steps. Each fact is kept with its ID\nand when it was saved, nothing else. A learner keeps their newest 200 facts. Without `user`, Memory steps\nare skipped. `user` must be a pseudonymous ID: 1 to 128 letters, digits, dots, dashes, underscores or colons,\nstarting with a letter or digit, such as `s-0412` or `my-app:student-0001`. A name or an email address\ngets 400 `invalid_learner`. VIABLE accepts any ID of up to 128 characters without control characters. Every agent a server runs shares its memory: a learner's facts from o",{"id":1807,"title":1808,"text":1809},"what-is-different-from-viable","What is different from VIABLE","The request: **`input`, `message`, `messages`** work as on VIABLE, as text: a string, text parts, or messages.\nA picture or file part gets 400 `unsupported_input`: an app's run takes text, and only a learner page takes\nrecordings and attachments. **`context`** is checked as on VIABLE (an object, at most 128 KiB, 16 levels deep). The fields\nVIABLE composes itself, such as the teacher's instructions, are removed, as on VIABLE. It isn't\nkept as what the learner had open. **`user`**: see Memory. It makes no learner record. **`learner_group`** is checked as on VIABLE, then not used. **`sources`** a",{"id":1811,"title":1812,"text":1813},"knowledge-search","Knowledge search","VIABLE searches a knowledge base by keywords and by meaning together (embeddings, then a reranking\nmodel), unless the knowledge base is set to keywords only. It brings back a passage's neighbours from\nthe same section, up to 4,000 characters. It converts PDFs, Office files and pictures to text, reads\nand refreshes websites, and can link a citation to an uploaded file, at a PDF's page. The Engine searches by keywords (BM25), as VIABLE does a keyword-only knowledge base, in 1,200-character\npassages, without neighbours. With a model for searching by meaning it also asks your provider's `\u002Fembeddin",{"id":1815,"title":1816,"text":1817},"what-you-don-t-get-without-viable","What you don't get without VIABLE","the editor, its tests and run history; accounts, projects, API keys, and limits per key; consent, studies and their conditions; the Learners view, the research export and its measures over time; request logs; hosted file conversion, reranking and website crawling; background jobs, idempotent retries and ratings; credits and billing. The agent engine, VETTING, How the AI helps and the formats are the same code VIABLE runs.",{"id":1819,"title":1820,"text":1821},"going-back","Going back","A package can go back to VIABLE: import it there. What a self-hosted server recorded can't be imported into\nVIABLE's studies. A new version on VIABLE is a new download: run `deploy` again with `--force`, and deploy. This page comes from the open repository: [engine\u002Fdocs\u002Fself-hosting.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fengine\u002Fdocs\u002Fself-hosting.md). To change it, change it there.",{"path":1823,"title":606,"description":1824,"updated":131,"minutes":67,"sections":1825},"engine\u002Ftry","Or, in a clone, viable-engine try, with a package or an agent file if you have one.",[1826,1828,1832,1836,1840,1844],{"id":17,"title":17,"text":1827},"Or, in a clone, `viable-engine try`, with a package or an agent file if you have one. It opens a page on\nyour own computer with six example courses (or what you named). They are the real learner pages, with one\nthing added beside them: a panel called **What the AI did**. Ask the fractions tutor \"just tell me the answer\" and watch. You see what the teacher told the AI, the\ndraft, the reviewer striking it with its reason, the rewrite, and what reached the learner. Change how far\nthe AI may help, ask again, and the reply changes.",{"id":1829,"title":1830,"text":1831},"demo-mode-and-your-own-model","Demo mode, and your own model","With no key it runs in **demo mode**: the model's words are scripted, and everything around them is the real\nharness. No model is called, and what you type stays on your computer. With `OPENAI_API_KEY` or\n`OPENAI_BASE_URL` set, it runs on your model, and says so. `--demo` keeps it scripted anyway. The examples\nname VIABLE's models, so add `--model \u003Cid>` if your endpoint doesn't have them. On your own model, a package with misconceptions to listen for costs one more call per message: it\nasks the model which of them the message shows. Learners' names and identifiers are never sent. Flag What it ",{"id":1833,"title":1834,"text":1835},"what-s-on-the-page","What's on the page","**The panel.** The teacher's settings, then each message as it happens. A small control changes how far the\nAI may help, and the goal, for this try only. **The teacher's page** (`\u002Fteacher`), with a made-up class from `examples\u002Fclass\u002F`. Accepting a suggestion\nchanges the try-out's settings too. With your own package it shows what happened in the try-out. **Resources** (`\u002Fresources`): the catalog, the connected ones first, with what each does. **Take it live**: the commands from `viable-engine deploy`, and a prompt to give an AI assistant. The density lab's frame loads from developers.viablelab.",{"id":1837,"title":1838,"text":1839},"a-static-copy-for-a-website","A static copy for a website","This writes the same pages, with a small script answering their requests in the visitor's browser from the\nscripted replies, using the real engine. Put the folder on any static host. `--base` is the path it will be\nserved under; leave it out for a site's root. Nothing a visitor types leaves their browser. The script is\nwritten only into a copy: no server route serves it. **A copy publishes what it runs on.** The real engine answers in the visitor's browser, so all of it is in a\nfile anyone can read: each agent's instructions, every question with its answer key, hints and explanations,\nand what",{"id":1841,"title":1842,"text":1843},"the-panel-on-your-own-server","The panel on your own server","It is off by default. It carries instructions, drafts and reviewers' reasons, so a learner must never get it.\nThe route decides on each request, never the page. `viable-engine serve ... --panel` grants it to whoever holds the token. With no `--token-env`, the server\nmakes a token at start and prints it once, so the grant is never decided by a header. Sign in at `\u002Fteacher`;\nits links open a page with `?panel=1`, which is what asks for the panel. A page without it stays a learner's\npage, even in the browser you signed in on. That browser is the thing to watch on a shared computer. The session en",{"id":1845,"title":1846,"text":1847},"test-results-as-a-page","Test results as a page","This serves the results on this computer until you press Ctrl-C. Add `--out report.html` for one file instead. This page comes from the open repository: [engine\u002Fdocs\u002Ftry.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fengine\u002Fdocs\u002Ftry.md). To change it, change it there.",{"path":1849,"title":1850,"description":1851,"updated":13,"minutes":1546,"sections":1852},"engine\u002Fdeploying","Putting this in front of learners","This guide is for anyone who puts the Engine or the Harness in front of learners: a school or district, a researcher, or a developer building a learning…",[1853,1855,1859,1863,1866,1870,1874,1878,1882,1886,1890,1894,1898],{"id":17,"title":17,"text":1854},"This guide is for anyone who puts the Engine or the Harness in front of learners: a school or district, a\nresearcher, or a developer building a learning app on them. It says what the code itself\nguarantees, and what it leaves to you. To run agents and courses built on VIABLE on your own server,\nbehind the same calls your apps make to VIABLE, see [self-hosting.md](\u002Fdocs\u002Fengine\u002Fself-hosting). **This is not legal advice.** It names questions a deployment has to answer, not the answers for\nyour learners, your school or your country. Using this code does not make a deployment compliant\nwith any law",{"id":1856,"title":1857,"text":1858},"what-the-code-guarantees","What the code guarantees","Each guarantee is about this repository's own code (the engine, VETTING, the teaching layer, the\nmeasures, the formats and the CLI), with its limits. **No learner's name or identifier is added to what is sent to a model.** A name on a class list stays in your store, and is shown only behind the token and to that learner. A local run's learner\nID (`--learner`, for memory) and a section's ID choose what is kept and which teaching settings\napply; neither goes to a model. Learning records carry a pseudonymous ID (on the Engine's pages, the learner's personal code, which also signs in, so keep expo",{"id":1860,"title":1861,"text":1862},"what-a-local-run-sends-and-what-it-keeps","What a local run sends, and what it keeps","What Goes to Kept on the machine The learner's message and earlier messages, the agent's instructions, the teaching settings, `--context` where a step reads it, and what earlier steps found (passages, pages, recalled facts) your endpoint's `\u002Fchat\u002Fcompletions` in the trace Text a Speech step reads aloud your endpoint's `\u002Faudio\u002Fspeech` the audio file, in the media folder A picture's prompt: text written from the learner's message and models' replies, as the Image step's prompt says; for an edit, the pictures it changes (their addresses or data) your endpoint's `\u002Fimages\u002Fgenerations` or `\u002Fimages\u002Fe",{"id":1864,"title":1865,"text":17},"what-you-still-have-to-do","What you still have to do",{"id":1867,"title":1868,"text":1869},"terms-and-consent","Terms and consent","Tell learners, and for minors their parents or guardians, what the AI does, which model provider\nreceives what they write, what is kept, for how long, and who sees it, in your terms and privacy\nnotice, before they use it. For children, get the consent the law requires before collecting their data. In the United States,\nCOPPA applies to online services' collection of data from children under 13, and the FTC lets a\nschool consent in parents' place only when the data is used for the school's own educational\npurpose. Elsewhere the age and the form of consent differ (for online services that rely o",{"id":1871,"title":1872,"text":1873},"privacy-laws","Privacy laws","Which laws apply depends on who the learners are, where they are, and who runs the deployment. Among\nthem: **FERPA**, for education records at schools that receive US federal funds: a provider working for\na school is usually allowed in as a \"school official\" under the school's direct control, by\ncontract. **COPPA**, above, and the FTC's rule under it. **State student-privacy laws**, such as California's SOPIPA, New York's Education Law 2-d and\nIllinois's SOPPA, many of which require a data privacy agreement with the school or district (the\nStudent Data Privacy Consortium's National Data Privac",{"id":1875,"title":1876,"text":1877},"data-agreements-with-model-providers","Data agreements with model providers","Every endpoint you configure receives learners' words, and the image endpoint receives prompts\nwritten from them. Before learners use it: have an agreement with each provider (chat, speech and images) that covers data processing, what\nthe provider keeps and for how long, whether it trains on your data, where the data is processed,\nand its subprocessors; check that the provider's terms allow use by or for the ages of your learners; with an open-weight model on your own servers, the text stays with you, and that server's logs and\nbackups are learner data too.",{"id":1879,"title":1880,"text":1881},"retention-and-deletion","Retention and deletion","Set how long you keep traces, media files, memory files and anything your hooks record, and delete\nthem on that schedule: they are kept until you do. Answer a request to delete a learner's data everywhere it is: their memory file, the traces and\nlearning records that carry their ID, their audio and pictures, and what your providers keep. Keep the table that links pseudonymous IDs to students apart from the rest of the data, under the\nschool's control, so that the data alone doesn't say who a learner is. A class list in the Engine's console is that table,\nin the same store: it joins each name t",{"id":1883,"title":1884,"text":1885},"logging-and-where-traces-live","Logging and where traces live","A trace holds what learners wrote and what the AI replied, the instructions each step sent, picture\nprompts and file paths; a step's inputs leave out values under names like `api_key`, and media\nappear as references, not bytes. Treat traces, `run --json` output, the teacher view's HTML and\n`hooks.logProviderCall` records as learner data: store them where your agreements allow, with\naccess limited to the people who need them. Keep learners' text out of error trackers, analytics and shared logs unless they are covered too. Keep API keys in the environment (`OPENAI_API_KEY`) or in the console, wh",{"id":1887,"title":1888,"text":1889},"unverified-and-needs-attention-replies","Unverified and needs-attention replies","Decide what a learner sees when a reply is `unverified`: the fractions example sends a short message\nthat asks the learner to rephrase or ask their teacher, instead of the reply the reviewer didn't\napprove. Don't send an unverified reply as if it had passed. An `attention` outcome means the reply model saw something that may need a person, such as a\nlearner in distress. Nothing here notifies anyone. Decide who is told, how fast, and how that fits\nyour school's safeguarding and reporting procedures, and give learners resources that fit where they\nare. Read the replies that didn't pass: the teac",{"id":1891,"title":1892,"text":1893},"accessibility","Accessibility","The Harness returns text, and the Engine audio and pictures too. A page you build, or the Engine's pages under your own settings, are yours to check. Build it to\nWCAG 2.1 AA at least (in the United States, the ADA's rule for state and local governments, which\ncovers public schools and colleges, adopts it): give each picture a text alternative (an Image step\ngives each picture a `description`), offer the text of anything read aloud, and make sure the\ninterface works with a keyboard and a screen reader.",{"id":1895,"title":1896,"text":1897},"what-a-link-can-spend","What a link can spend","Whoever holds a course's link can send messages, and your model key pays for them. An activity answers at most 5,000 messages a day, and each learner at most what you set in a course's Settings. To keep strangers out, use personal codes: then only codes on your class list open the course.",{"id":1899,"title":1900,"text":1901},"a-security-contact","A security contact","Publish how people can report a security or privacy problem in your deployment (a `security.txt`\nfile, RFC 9116, is one way), and answer them. Report a problem in this code privately, as [SECURITY.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002FSECURITY.md) says, never in a\npublic issue, and keep to its latest minor release, which receives fixes. This page comes from the open repository: [engine\u002Fdocs\u002Fdeploying.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fengine\u002Fdocs\u002Fdeploying.md). To change it, change it there.",{"path":1903,"title":1904,"description":1905,"updated":131,"minutes":1546,"sections":1906},"engine\u002Flearner-pages","The pages learners see","It prints where each page opens.",[1907,1909,1913,1917,1919,1923,1927,1931,1935],{"id":17,"title":17,"text":1908},"`viable-engine serve` and every scaffold from `viable-engine deploy` serve the same pages VIABLE does,\nfrom the same code: an agent's chat page, the chat button for your website, a course's home and its\nactivities (questions, writing, reading, embedded pages, with the AI beside them). Page Address A chat page `\u002Fchat\u002F\u003Cagent>` The chat button `\u003Cscript src=\"https:\u002F\u002Fyour-server\u002Fembed.js\" data-agent=\"\u003Cagent>\" async>\u003C\u002Fscript>` A course `\u002Fcourse\u002F\u003Ccourse>` An activity `\u002Fcourse\u002F\u003Ccourse>\u002F\u003Cactivity>` The teacher's page `\u002Fteacher`, behind the token. With none set, the server makes one at start and prints ",{"id":1910,"title":1911,"text":1912},"what-travels-from-viable","What travels from VIABLE","A package downloaded with its files carries each agent's profile (name, greeting, starters, look) and\nchat page settings (limits, allowed sites), the course and its activities, and a snapshot of each\ncrawled page's text. It never carries the key behind a chat page, learners, class lists, sections or study data. Setting On your server Allowed sites enforced: who may frame the page (`frame-ancestors`), and whose page the button draws on Messages per visitor, per minute and per day enforced The page's daily message cap enforced A high ceiling per address enforced, so a class on one address isn't ",{"id":1914,"title":1915,"text":1916},"who-a-learner-is","Who a learner is","Each course says how, on its People tab in the console (the package's `identify`): Way How it works Each browser (`browser`, the default) Nobody signs in. A browser holds a random ID, and the server gives it a personal code, shown on the home. \"Not you?\" asks for a code, or starts fresh. The code brings the learner's work back on another computer Personal codes (`code`) You add your class to the list, and print a card for each learner. Only a code on the list opens the course, typed with or without its dash, in any case, or from the QR code on the card. A package carries no class list, so a co",{"id":313,"title":314,"text":1918},"A section is a class period or a group, on People, with a link of its own (`?section=\u003Ckey>`). Sections are in the\nstore, not the package. A learner's section comes from the class list, from a section's link, or from their own\nchoice, once (`sections_from`: `roster`, `link`, `choose`); only a teacher changes it after that. Someone who opens the\ncourse's own link where sections come from links is asked which class they're in, once. A section can have its own help level and a note for the AI, on the activity (`teaching.sections`); they reach only that\nsection's learners' runs. An activity can als",{"id":1920,"title":1921,"text":1922},"progress","Progress","Progress is a course's grid of who has done what, one learner's conversations, drafts and answers, read only, and for\neach activity with an AI, what to look at: how replies went, hard moments and suggested changes, each accepted in one\nbutton as a new version of the course, and suggested again only if what learners do after it calls for it. Who was only\ntrying the course out isn't counted. What it exports is the records, which name each learner by their code, and the class list as a CSV,\nthe only file that joins names and codes. A proxy's `--learner-header` needs `--token-env` too. Who it name",{"id":1924,"title":1925,"text":1926},"what-is-kept-and-for-how-long","What is kept, and for how long","With `--data-dir` (or a store on a cloud), drafts, events, answers and turns are kept in the shape\n`viable-harness view` and `suggest` read. `--keep-days \u003Cn>` deletes, that long after each last changed, turns and\ntheir traces, conversations, drafts, answers, highlights and events. A learner's code, name and how far they got stay, so\na learner can come back, to an empty draft if they were away that long. `forget` removes that learner's work, conversations, runs, memory and gateway records, and their place on the class\nlist. A chat page's visitors are anonymous, so nobody can name one of them to",{"id":1928,"title":1929,"text":1930},"what-isn-t-here-and-what-happens-instead","What isn't here, and what happens instead","On VIABLE Here Voice, reading replies aloud Offered when a model is set for hearing and for speaking ([Models](\u002Fdocs\u002Fengine\u002Fself-hosting#models-set-up-in-the-browser)). Until then, the microphone isn't offered, and Speech steps pass on. A browser asks for the microphone only on `localhost` or over HTTPS Pictures and PDFs attached to a message Offered when the reply model is set and switched on as reading them. Until then, the button isn't offered Spoken questions Not asked: recordings are kept and scored on VIABLE Studies, consent, conditions None: no notice, form or \"About the study\" Previews",{"id":1932,"title":1933,"text":1934},"the-website-a-knowledge-base-reads","The website a knowledge base reads","A package's snapshot is what is searched here. To read the sites again, on the rules the package lists\n(a list's pages, a sitemap's, or a crawl under its path prefix, up to its page limit): It reads one page at a time with a pause, as `robots.txt` asks, stops reading a site that answers 429 or\n503, and refuses a name that resolves to a private address. A page it can't reach keeps its snapshot and\nis reported; a page that answers 404 or 410 leaves it. Serve or deploy `.\u002Frefreshed`, and run it on your\ncloud's own schedule to keep it current. A citation links to a page at its own address. A file ",{"id":1936,"title":1937,"text":1938},"on-your-own-host","On your own host","`createHandler({ routes: [learnerRoutes(options)] })` (`server\u002Flearner.js`) is web-standard. Its pages'\nbrowser files are built by `npm run build:assets` into `dist\u002F`, which the npm package and the image carry\nand this repository doesn't, since they bundle third-party libraries (NOTICE). `deploy` puts them beside\nthe package on Cloudflare and in the function elsewhere. Without them pages still work: replies show as\nplain text, and Reading panels are left out. To let someone read what the AI did on a turn (its instructions, drafts and reviewers' reasons), set\n`traces` in those options: `'token'",{"path":1940,"title":1941,"description":1942,"updated":131,"minutes":67,"sections":1943},"engine\u002Fstep-manifests","Step manifests","Each step type an agent graph can use has one manifest in src\u002Fagents\u002Fsteps\u002F\u003Ctype>.js.",[1944,1946,1949,1953,1957,1961,1965,1969,1973],{"id":17,"title":17,"text":1945},"Each step type an agent graph can use has one manifest in `src\u002Fagents\u002Fsteps\u002F\u003Ctype>.js`. It is\nplain data (a default export with no functions), so it serializes to JSON unchanged. One manifest\ndrives the step settings panel and canvas card in the Developer Portal, the step library, the AI\nbuilder's notes, `GET \u002Fv1\u002Fdeveloper\u002Fagents\u002Fgraph-schema` (and so `viable developer agents schema`),\nthis harness's `schemas\u002Fsteps.json` and the step docs. Runtime validation stays in\n`src\u002Fagents\u002Fgraph.js`; `test\u002FstepManifests.test.js` keeps the two in agreement. `npm run steps:build` writes `schemas\u002Fsteps.json",{"id":1947,"title":1948,"text":17},"a-manifest","A manifest",{"id":1950,"title":1951,"text":1952},"settings","Settings","**Levels.** `setup` is what every author of this step decides; it is always shown. `option` and\n`recovery` are shown when set (or when their value differs from the default) and otherwise listed\nunder **Add option** in their section; `recovery` is the **When something goes wrong** section\n(retries, fallbacks, what happens on an error). `advanced` behaves like `option` but sorts last in\nAdd option. Every setting a manifest lists must be reachable from the panel: a setting is never\nhidden only because it is unset. **Widgets.** `text`, `textarea`, `prompt` (long instructions, edited in the full-wi",{"id":1954,"title":1955,"text":1956},"conditions","Conditions","`when` (and `defaults[].when`, `sentence[].when`, `outputs[].when`) is data, so the runtime, the\nportal, the CLI and the docs evaluate it the same way: Form Holds when `{ set: 'items' }` the value is not null, undefined, `''` or `[]` `{ unset: 'items' }` it is `{ present: 'items' }` the value is not null or undefined (`''` counts: an Image step with `items: ''` is in list mode) `{ absent: 'items' }` it is null or undefined `{ is: { mode: 'full' } }` the value equals it; a list matches any; `null` in the list matches unset `{ has: { checks: ['off_topic', 'custom'] } }` the value is a list holdi",{"id":1958,"title":1959,"text":1960},"parts","Parts","Built-in editors with behaviour no generic widget has, placed by level like a setting:\n`{ id: '@pictures', level: 'setup' }`. Known parts: `@pictures` (an Image step's one prompt or\nlist), `@outputs` (an LLM's answer format), `@cases` (a Branch's cases), `@loop` (a Loop's count\nor condition), `@outcomes` (VETTING's outcomes), `@routes`, `@paths`. A part may name the setting\nit sits after: `{ id: '@pictures', level: 'setup', after: 'model' }`. Parts that are a section of their own in the panel (`@outputs` as Answer format, `@cases`, `@outcomes`) show as that section; their level only says how e",{"id":1962,"title":1963,"text":1964},"the-sentence","The sentence","A list of segments joined in order. Each has `text` with `{placeholders}` and an optional `when`.\nA segment whose placeholders are all empty is left out. `{key}` is a setting, formatted by its widget: a reference shows as its step and field (\"tutor →\npictures\"), long text as its first clause, a select as its option label, a list as \"a, b and c\".\n`{@name}` is a computed phrase the portal provides: `@outputs`, `@routes`, `@paths`, `@cases`,\n`@tools`, `@checks`, `@assignments`, `@values`, `@exits`, `@agent`, and for a Component step\n`@component` (its manifest's label) and `@sends` (what each fiel",{"id":1966,"title":1967,"text":1968},"outputs","Outputs","`path` is read as `{{\u003Cstep id>.\u003Cpath>}}`. `type` is `string`, `number`, `boolean`, `list` or\n`any`. `item` names the fields of a list's items (`picture`, `unfinished`). An LLM's named fields,\na State step's values, a Values step's values, a Loop's counter and a For each step's item come\nfrom the step's own config and are added by the portal.",{"id":1970,"title":1971,"text":1972},"presets","Presets","Shown when the step is added from the library. A preset only fills settings; the step is the same\ntype either way.",{"id":1974,"title":1975,"text":1976},"rules-the-tests-enforce","Rules the tests enforce","Every type in `NODE_TYPES` has exactly one manifest, and every manifest is a node type. Every manifest serializes to JSON and back unchanged. Setting keys are unique; levels, widgets, part ids and condition forms are known. Every placeholder in a sentence names a setting or a known `{@name}`. Tips are at most 160 characters; labels at most 40. Numeric bounds agree with `graph.js` where it checks them. `schemas\u002Fsteps.json` equals what `npm run steps:build` writes. This page comes from the open repository: [engine\u002Fdocs\u002Fstep-manifests.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fen",{"path":1978,"title":1979,"description":1980,"updated":13,"minutes":1981,"sections":1982},"engine\u002Fguide","Using the command lines","The reference for the command line, what a local run does, the services a host passes in, the open formats and the measures.",15,[1983,1985,1989,1993,1997,2001,2004,2008,2012,2016,2020,2024,2026,2029,2033,2036,2040,2044],{"id":17,"title":17,"text":1984},"The reference for the command line, what a local run does, the services a host passes in, the open\nformats and the measures. The [README](\u002Fdocs\u002Fstart\u002Fopen-source) is the overview; [components.md](\u002Fdocs\u002Fharness\u002Fcomponents),\n[mcp.md](\u002Fdocs\u002Fharness\u002Fmcp), [xapi.md](\u002Fdocs\u002Fharness\u002Fxapi) and [step-manifests.md](\u002Fdocs\u002Fengine\u002Fstep-manifests) go deeper.",{"id":1986,"title":1987,"text":1988},"the-command-line","The command line","`viable-harness --help` has every flag. `viable-engine try [package...]` opens the real pages on this\ncomputer with what the AI did beside them; `--demo --tour` plays the scene by itself and `--export \u003Cdir>`\nwrites a static copy of demo mode ([try.md](\u002Fdocs\u002Fengine\u002Ftry)). `view` and `suggest` also take a folder, which stands for\nevery JSON file in it. (Until the packages are on npm, use `npx github:VIABLE-Learning-Lab\u002Fviable` in place of `viable-harness`: it takes every command.\nFrom a clone, `node engine\u002Fbin\u002Fviable-engine.mjs`.) **run**: a `.viable.zip` (a course downloaded with its files) run",{"id":1990,"title":1991,"text":1992},"what-runs-locally","What runs locally","Steps In a local run Input, LLM, Condition, Router, Agent, VETTING, Guardrail, Transform, State, Loop, For each, Parallel, Ask, Reply (output), Note Run as on VIABLE. An Agent step's memory tools work as Memory steps do; in a course package it searches the package's knowledge bases (`search_knowledge`, not browsing or reading whole documents) and calls its other agents. Fetch URL (http) Reads JSON, text and HTML pages, which become Markdown (headings, paragraphs, lists, links, images, table rows) without scripts, styles or site chrome. Knowledge In a course package (a folder or its `.viable.zi",{"id":1994,"title":1995,"text":1996},"serving-it","Serving it","`serve` is for one machine. For a cloud, `inspect` says what a package needs, `deploy \u003Ctarget>` writes a folder\nready to deploy, and `verify` checks it from outside ([self-hosting.md](\u002Fdocs\u002Fengine\u002Fself-hosting)). In code,\n`createHandler({ agents, runtime, store, options, routes })` is a function from a web Request to a Response, and\n`createApp({ bytes, vars, store, models })` builds one from a package's bytes. `serve --panel` adds the panel to\nyour own server, for the token holder, on a page asked for with `?panel=1`. `test ... --html` shows results as a page.",{"id":1998,"title":1999,"text":2000},"learners-pages","Learners' pages","A package carries each agent's look and chat page settings, and `serve` hosts them: `\u002Fchat\u002F\u003Cagent>`, `\u002Fembed.js`,\n`\u002Fcourse\u002F\u003Ccourse>`, and `\u002Fteacher` and `\u002Fconsole` behind the token (with none set, one made at start and printed once). Limits and\nallowed sites are enforced. A learner is who the course says: a browser with a code they can re-enter, a personal code from its\nclass list, or a name they type; or whoever has a class code (`--class-code-env`), or who a proxy says (`--learner-header`). The\nconsole sets up models, courses, class lists, chat pages and knowledge in the browser. `viable-eng",{"id":2002,"title":2003,"text":17},"from-code","From code",{"id":2005,"title":2006,"text":2007},"services","Services","The engine takes whatever only a host has as services, so a host decides where models, knowledge,\nmemory and media come from: `runAgent(doc, { services: { callChatModel, searchKnowledge, memory,\nresolveSubAgent, generateImage, generateSpeech, transcribeAudio, toMarkdown, models } })`, with the\nshapes `src\u002Fagents\u002Fengine.js` calls. A local run brings its own for models\n(`openAICompatible({ baseUrl, apiKey, model, fetch })`), speech and pictures (`openAISpeech({ baseUrl, apiKey,\nmodel, fetch, save })` and `openAIImages({ baseUrl, apiKey, model, fetch, save })`, saving with\n`mediaFiles(dir)`), mem",{"id":2009,"title":2010,"text":2011},"in-another-harness-or-assistant","In another harness or assistant","For a host that keeps its own harness, assistant or agent SDK: **An MCP server.** `viable-harness mcp` serves its tools over stdio: `check_reply`,\n`teaching_instructions`, `find_misconceptions`, `compute_measures`, `validate_document`,\n`run_agent`, `run_agent_tests`, `draft_tests`, `calibrate_judge`, `suggest_changes`,\n`list_components` and `find_public_goods`. A client starts it as\n`node \u002Fpath\u002Fto\u002Fviable\u002Fharness\u002Fbin\u002Fviable-harness.mjs mcp`, with `OPENAI_BASE_URL` and\n`OPENAI_API_KEY` for the tools that call models. The host decides whether a reply is checked and\nwhat is kept; only an agent the",{"id":2013,"title":2014,"text":2015},"the-open-formats","The open formats","JSON Schema (draft 2020-12) in `schemas\u002F`, each with an example and published at its `$id`,\n`https:\u002F\u002Fdevelopers.viablelab.org\u002Fschemas\u002F\u003Cname>.schema.json`: Schema What Example `agent.schema.json` An exported agent (`.agent.json`, format `viable.agent`): its graph, profile and what it requires `schemas\u002Fexamples\u002Fagent.example.json` `course-package.schema.json` A course with its agents and question sets (`viable.course-package`) the six files in `examples\u002F` `learning-events.schema.json` One learner's events and turns `schemas\u002Fexamples\u002Flearning-events.example.json` `teaching.schema.json` An activit",{"id":2017,"title":2018,"text":2019},"the-measures","The measures","`measureLearner({ events, turns })` returns the research export's learner metrics (messages,\nsessions, think time, revision, pasting, accuracy, mastery, wheel-spinning, ...), mastery per skill,\nand how the learner wrote in each writing panel. Each metric's definition, computation, rationale,\ncaveats and sources are exported as `METRICS`, `WRITING`, `PANELS` and `CITATIONS`, from the same\nfile VIABLE's export and dashboard use (`src\u002Futils\u002FresearchMetrics.js`).",{"id":2021,"title":2022,"text":2023},"the-teacher-view","The teacher view","`viable-harness view` (or `teacherView({ teaching, records, runs, title, suggestions })`) makes one HTML page for a\nteacher who doesn't read JSON. It opens on what happened, in sentences and a row of marks, one group for each reply\nreviewed and one mark for each round. Then come How the AI helps as it was set, with each section's own level and\nnote; what VETTING did, with every reply that was sent back as a marked-up draft (the draft struck, the reviewer's\nreason beside it, the rewrite); each learner's measures, by the ID their record carries, as a row of ticks and\ncrosses and small bars, with",{"id":372,"title":373,"text":2025},"The loop's fourth step: `viable-harness suggest \u003Crecords and runs...>` (or `suggestChanges({ teaching,\nruns, records })`) proposes changes to How the AI helps from the evidence, each with the evidence\nbeside it, and the teacher view shows them. Rules, no model, so each one says exactly why: Rule When Proposes Answers given away at least 30% of 5 or more reviewed replies failed their first VETTING review for giving too much away one step less help (from explain to hint, ...) Replies unverified at least 20% of 5 or more replies never passed review nothing: the teacher reads those replies Misconc",{"id":2027,"title":1459,"text":2028},"components","Other teams' work joins the harness as a component in one of six slots, with a manifest saying what\nlearner data it receives, where it sends it, its license, citation and evidence\n([components.md](\u002Fdocs\u002Fharness\u002Fcomponents); each built-in component has a page in `docs\u002Fcomponents\u002F`). A\ncomponent that runs out of process, as an HTTP endpoint or a stdio MCP server, is called with\n`callComponent(manifest, payload)` or `viable-harness component \u003Cmanifest.json> --input\npayload.json`. The harness sends only the fields `receives` lists, calls an HTTP entry only on a host\nin `sends_to`, and stops at a t",{"id":2030,"title":2031,"text":2032},"the-public-goods-catalog","The public goods catalog","[catalog.md](\u002Fdocs\u002Flibrary) lists open datasets, benchmarks, knowledge graphs, standards, evaluators,\nmodels, tools, open content and learning data repositories for K-12 AI, one file each in `catalog\u002F`\n(`schemas\u002Fcatalog-entry.schema.json`), each with its license and a note on gated, non-commercial,\nmixed or conflicting terms, how to get it, whether it is released, the slot it would fill and the\ndate it was checked. `searchCatalog(query, { kind, slot, open, connected })`, `viable-harness catalog` and the\nMCP tool `find_public_goods` search it with no network; `open` keeps what anyone may downlo",{"id":2034,"title":869,"text":2035},"agent-tests","The loop's fifth step: cases written from the teacher's intent, run again after every change.\n`viable-harness draft-tests \u003Cpackage.json | teaching.json> --model \u003Cid>` (or `draftCases`, or the\n`draft_tests` MCP tool) has a model draft cases from How the AI helps (asking for the answer, a\nlisted misconception, a section's own level, distress, unsafe and off-topic requests), each checked\nlike a saved case, with malformed drafts listed and the reason; a person reads every draft before it\nruns. `viable-harness tests-from-records \u003Crecord.json...>` (`casesFromTurns`) takes cases from\nlearners' past t",{"id":2037,"title":2038,"text":2039},"conformance-and-xapi","Conformance and xAPI","`conformance\u002F` holds language-neutral JSON fixtures: documents in each format, graphs, expressions,\nteaching settings, VETTING's reviewer outputs and prompts, and learning records, each with what the\nharness does with it. Another implementation, such as a port of VETTING or an engine that reads\nVIABLE's formats, runs the same cases to show it agrees; the fixtures decide when two disagree\n([conformance\u002FREADME.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fharness\u002Fconformance\u002FREADME.md)). `npm test` runs them against the harness,\n`viable-harness conformance [suite]` prints pass or F",{"id":2041,"title":2042,"text":2043},"the-examples","The examples","`examples\u002F` holds six course packages: fractions practice, a PDF reading with a Socratic tutor,\nargument writing, English speaking practice, a density lab on an embedded page, and a library\nresearch desk. All six run locally as they are (`test\u002Fexamples.test.js` runs each on a scripted\nendpoint): the library desk downloads its four guides and answers from them with citations, and the\nEnglish partner's reply is read aloud on your endpoint's `\u002Faudio\u002Fspeech`; its Transcribe step is\nskipped, since a local run sends typed text, not a recording. On VIABLE the reading tutor and the\ndensity lab coach a",{"id":2045,"title":2046,"text":2047},"how-it-relates-to-the-viable-developer-platform","How it relates to the VIABLE Developer Platform","The [VIABLE Developer Platform](https:\u002F\u002Fdevelopers.viablelab.org) runs this same engine, teaching\nlayer and measures, and adds what running it for many projects takes: accounts, a visual workflow editor,\nstudies with consent, coding and the research export; learners open its\nactivities on the [VIABLE Learning Platform](https:\u002F\u002Fviablelearning.org). It passes its own services\n(model calls on its provider keys, media, billing fields, its model catalog) and runs exactly the code\nin this repository. This page comes from the open repository: [docs\u002Fguide.md](https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fvia",{"meta":2049,"blocks":2050,"headings":2054,"sections":2062},{"path":1675,"title":1676,"description":1672,"updated":13,"minutes":835},[2051],{"type":2052,"html":2053},"html","\u003Cp>For a school, a library or a lab that wants this running on its own machines, on any model. Nothing here needs VIABLE's hosted\nservice, and nothing is held back because it is open: add a model and what it makes possible turns on.\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fmain\u002Fdocs\u002Fimages\u002Fconsole.gif\" alt=\"The Engine's console. A teacher writes the goal of an activity and how far the AI may go, types what a learner might say, and sees what the AI would be told at that moment. Then a learner's page opens with that activity, and the AI replies by asking how they got their answer.\">\u003C\u002Fp>\n\u003Cp>A real server, from empty. Only the model's words are scripted here.\u003C\u002Fp>\n\u003Ch2 id=\"ten-minutes-from-nothing-to-a-class\">Ten minutes, from nothing to a class\u003Ca class=\"doc-anchor\" href=\"#ten-minutes-from-nothing-to-a-class\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cdiv class=\"guide-code\">\u003Cdiv class=\"guide-code-head\">\u003Cspan>sh\u003C\u002Fspan>\u003Cbutton type=\"button\" data-copy-code aria-label=\"Copy sh code\">Copy\u003C\u002Fbutton>\u003C\u002Fdiv>\u003Cpre>\u003Ccode class=\"hljs\">npx github:VIABLE-Learning-Lab\u002Fviable start\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\u003Cp>It keeps everything in \u003Ccode>~\u002F.viable-engine\u003C\u002Fcode>, and opens its console, signed in.\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Models.\u003C\u002Fstrong> Add a provider (OpenAI, OpenRouter, Ollama, LM Studio, or any OpenAI-compatible address), test it, and say what each\nof its models is for.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Courses.\u003C\u002Fstrong> Start from nothing, from an example, or from a file.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>An activity.\u003C\u002Fstrong> Say what learners do, the goal, and how far the AI may go. Type what a learner might say to see exactly what the\nAI would be told at that moment. \u003Cstrong>Try it\u003C\u002Fstrong>, as a learner, without being counted. Then open it.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>People.\u003C\u002Fstrong> Let anyone with the link in, or paste your class list and print a card for each learner with their personal code.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Share\u003C\u002Fstrong> the link or its QR code. A class on your network needs \u003Ccode>--host 0.0.0.0\u003C\u002Fcode>, on a network you trust, since that's plain\nhttp; a class anywhere needs a \u003Ca href=\"#where-it-runs\">server\u003C\u002Fa> on https.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Then \u003Cstrong>Progress\u003C\u002Fstrong> shows who has done what, what to look at, and any change the evidence suggests, which you can accept with one\nbutton.\u003C\u002Fp>\n\u003Cp>What you change reaches learners on their next message. There is nothing to restart and nothing to publish.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Or just say it.\u003C\u002Fstrong> With a model set, the console opens on \u003Cstrong>Ask\u003C\u002Fstrong>: say what you're teaching, and it makes the course, the activities and the questions, showing each step and what it did, and hands back cards to use, such as the link, its QR code and the class list. It can't open anything to learners or delete anything: that waits for your click, enforced on the server. A class list goes in its own box, and names never reach the model.\u003C\u002Fp>\n\u003Ch2 id=\"what-each-model-turns-on\">What each model turns on\u003Ca class=\"doc-anchor\" href=\"#what-each-model-turns-on\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cdiv class=\"guide-table\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Role\u003C\u002Fth>\n\u003Cth>Calls\u003C\u002Fth>\n\u003Cth>What it makes possible\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Writes replies\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fchat\u002Fcompletions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Replies, word by word. Photos and PDFs from learners, if you say this model reads them\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Reviews replies\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fchat\u002Fcompletions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A second model in an agent's check step\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Sets up classes \u003Cem>(optional)\u003C\u002Fem>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fchat\u002Fcompletions\u003C\u002Fcode>, with tools\u003C\u002Ftd>\n\u003Ctd>Ask. Otherwise it uses whichever writes replies\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Reads the moment \u003Cem>(optional)\u003C\u002Fem>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fchat\u002Fcompletions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A small model picks what each turn needs. Otherwise rules do, in English\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Hears learners\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Faudio\u002Ftranscriptions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The microphone: a voice message, dictation, a conversation by voice\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Reads aloud\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Faudio\u002Fspeech\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Replies read aloud, and an agent's Speech step\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Draws pictures\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fimages\u002Fgenerations\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>An agent's Image step\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Searches by meaning\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>\u002Fembeddings\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Knowledge search by meaning as well as keywords\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Cp>The console shows each one on or off, and why. Keys are encrypted where they are kept, and never come back to a browser.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Care or speed is yours to weigh.\u003C\u002Fstrong> A check holds a reply back until it passes, so a learner waits for each try: on our first run with a strict\nreviewer, 12 to 31 seconds a reply. An agent's page sets how many tries, Models sets how fast a reviewer, and Progress says how long learners\nhave been waiting.\u003C\u002Fp>\n\u003Ch2 id=\"a-chat-button-on-your-website\">A chat button on your website\u003Ca class=\"doc-anchor\" href=\"#a-chat-button-on-your-website\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>A library wants research help on its guides' pages, answering from its own guides, on its own server.\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Knowledge.\u003C\u002Fstrong> Make a knowledge base, add the guides as files or read them from your website.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Chat pages.\u003C\u002Fstrong> Make a page: its instructions, its knowledge base, how many messages a visitor gets.\u003C\u002Fli>\n\u003Cli>Copy the one line onto your site, and say which sites may show it.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cdiv class=\"guide-code\">\u003Cdiv class=\"guide-code-head\">\u003Cspan>html\u003C\u002Fspan>\u003Cbutton type=\"button\" data-copy-code aria-label=\"Copy html code\">Copy\u003C\u002Fbutton>\u003C\u002Fdiv>\u003Cpre>\u003Ccode class=\"hljs\">\u003Cspan class=\"hljs-tag\">&lt;\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">src\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;https:\u002F\u002Fyour-server\u002Fembed.js&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">data-agent\u003C\u002Fspan>=\u003Cspan class=\"hljs-string\">&quot;research-help&quot;\u003C\u002Fspan> \u003Cspan class=\"hljs-attr\">async\u003C\u002Fspan>&gt;\u003C\u002Fspan>\u003Cspan class=\"hljs-tag\">&lt;\u002F\u003Cspan class=\"hljs-name\">script\u003C\u002Fspan>&gt;\u003C\u002Fspan>\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\u003Cp>Anyone who can open it can use it on your model keys, up to the limits you set.\u003C\u002Fp>\n\u003Ch2 id=\"from-the-developer-platform\">From the Developer Platform\u003Ca class=\"doc-anchor\" href=\"#from-the-developer-platform\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>On the Developer Platform, download the course or agent as a package with its files (Courses → ⋯ → Export). In the console:\n\u003Cstrong>Courses → From a file\u003C\u002Fstrong>. Or start with it:\u003C\u002Fp>\n\u003Cdiv class=\"guide-code\">\u003Cdiv class=\"guide-code-head\">\u003Cspan>sh\u003C\u002Fspan>\u003Cbutton type=\"button\" data-copy-code aria-label=\"Copy sh code\">Copy\u003C\u002Fbutton>\u003C\u002Fdiv>\u003Cpre>\u003Ccode class=\"hljs\">npx github:VIABLE-Learning-Lab\u002Fviable start course.viable.zip\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\u003Cdiv class=\"guide-table\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Travels\u003C\u002Fth>\n\u003Cth>Stays behind\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Activities, question sets, How the AI helps, agents and their look, chat page settings, knowledge files, website snapshots\u003C\u002Ftd>\n\u003Ctd>Learners and their work, class lists, keys, study data\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Cp>A workflow built on the visual editor runs here as it is: you can't redraw it in the console, and you can change its look and page.\nThe other way works too: \u003Cstrong>Settings → Download\u003C\u002Fstrong>, then import it on the Developer Platform, where models it doesn't have become\nits default, and it says so.\u003C\u002Fp>\n\u003Ch2 id=\"where-it-runs\">Where it runs\u003Ca class=\"doc-anchor\" href=\"#where-it-runs\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>viable-engine deploy &lt;target&gt; [package] --out &lt;folder&gt;\u003C\u002Fcode> writes a folder with its own README and exact commands, and you deploy it\nyourself. With no package, the server starts empty and you set it up at \u003Ccode>\u002Fconsole\u003C\u002Fcode>. A course changed there stays in the store.\u003C\u002Fp>\n\u003Cdiv class=\"guide-table\">\u003Ctable>\u003Cthead>\n\u003Ctr>\n\u003Cth>Target\u003C\u002Fth>\n\u003Cth>Keeps things in\u003C\u002Fth>\n\u003Cth>How far it has been tried\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>node\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a folder\u003C\u002Ftd>\n\u003Ctd>run by the tests\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>docker\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a volume\u003C\u002Ftd>\n\u003Ctd>run under Docker\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cloudflare\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>D1\u003C\u002Ftd>\n\u003Ctd>run with \u003Ccode>wrangler dev --local\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>firebase\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Firestore\u003C\u002Ftd>\n\u003Ctd>run in the Functions emulator; never deployed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>aws-lambda\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>an S3 bucket\u003C\u002Ftd>\n\u003Ctd>its handler tested; never deployed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cloud-run\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>a Cloud Storage bucket as a folder\u003C\u002Ftd>\n\u003Ctd>its image run under Docker; never deployed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\u003Ch2 id=\"what-it-keeps-and-who-sees-it\">What it keeps, and who sees it\u003Ca class=\"doc-anchor\" href=\"#what-it-keeps-and-who-sees-it\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Everything stays in your store. A learner's name lives only on the class list, and is never sent to a model. Learner records carry a\npseudonymous code. \u003Ccode>--keep-days &lt;n&gt;\u003C\u002Fcode> deletes what learners wrote after that many days, and\n\u003Ccode>viable-engine forget &lt;code&gt;\u003C\u002Fcode> deletes one learner. \u003Ca href=\"\u002Fdocs\u002Fengine\u002Fdeploying\">deploying.md\u003C\u002Fa> lists what the code guarantees, and what is\nstill yours to do, such as your agreement with each model provider.\u003C\u002Fp>\n\u003Ch2 id=\"more-than-the-console\">More than the console\u003Ca class=\"doc-anchor\" href=\"#more-than-the-console\" aria-label=\"Link to this section\">\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cdiv class=\"guide-code\">\u003Cdiv class=\"guide-code-head\">\u003Cspan>sh\u003C\u002Fspan>\u003Cbutton type=\"button\" data-copy-code aria-label=\"Copy sh code\">Copy\u003C\u002Fbutton>\u003C\u002Fdiv>\u003Cpre>\u003Ccode class=\"hljs\">viable-engine start                                 \u003Cspan class=\"hljs-comment\"># the console, on a folder of its own\u003C\u002Fspan>\nviable-engine serve course.viable.zip --port 8080   \u003Cspan class=\"hljs-comment\"># one package, with your flags\u003C\u002Fspan>\nviable-engine run agent.json --input \u003Cspan class=\"hljs-string\">&quot;...&quot;\u003C\u002Fspan>          \u003Cspan class=\"hljs-comment\"># one reply, in a terminal\u003C\u002Fspan>\nviable-engine inspect course.viable.zip             \u003Cspan class=\"hljs-comment\"># what it needs, before you run it\u003C\u002Fspan>\nviable-engine verify https:\u002F\u002Fyour-server            \u003Cspan class=\"hljs-comment\"># whether a server works\u003C\u002Fspan>\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\u003Cp>Apps call the same API as the hosted platform (\u003Ccode>POST \u002Fv1\u002Fagents\u002F&lt;slug&gt;\u002Fruns\u003C\u002Fcode>), so they move by changing an address.\n\u003Ca href=\"\u002Fdocs\u002Fengine\u002Fself-hosting\">self-hosting.md\u003C\u002Fa> is the reference: flags, the API, what differs from VIABLE. Others:\n\u003Ca href=\"\u002Fdocs\u002Fengine\u002Flearner-pages\">learner-pages.md\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fengine\u002Fdeploying\">deploying.md\u003C\u002Fa>, \u003Ca href=\"\u002Fdocs\u002Fengine\u002Ftry\">try.md\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Not here:\u003C\u002Fstrong> the visual workflow editor, studies with consent and the research export stay on the\n\u003Ca href=\"https:\u002F\u002Fdevelopers.viablelab.org\" target=\"_blank\" rel=\"noreferrer\">Developer Platform\u003C\u002Fa>. LTI isn't built. The Engine is built on the\n\u003Ca href=\"\u002Fdocs\u002Fharness\u002Foverview\">VIABLE Harness\u003C\u002Fa>, which you can use without any of this.\u003C\u002Fp>\n\u003Chr>\n\u003Cp>This page comes from the open repository: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FVIABLE-Learning-Lab\u002Fviable\u002Fblob\u002Fmain\u002Fengine\u002FREADME.md\" target=\"_blank\" rel=\"noreferrer\">engine\u002FREADME.md\u003C\u002Fa>. To change it, change it there.\u003C\u002Fp>\n",[2055,2056,2057,2058,2059,2060,2061],{"level":35,"id":1681,"text":1682},{"level":35,"id":1685,"text":1686},{"level":35,"id":1689,"text":1690},{"level":35,"id":1693,"text":1694},{"level":35,"id":477,"text":478},{"level":35,"id":1699,"text":1700},{"level":35,"id":1703,"text":1704},[2063,2064,2065,2066,2067,2068,2069,2070],{"id":17,"title":17,"text":1679},{"id":1681,"title":1682,"text":1683},{"id":1685,"title":1686,"text":1687},{"id":1689,"title":1690,"text":1691},{"id":1693,"title":1694,"text":1695},{"id":477,"title":478,"text":1697},{"id":1699,"title":1700,"text":1701},{"id":1703,"title":1704,"text":1705},1793871632302]