Changelog
What changed in the developer surface, newest first.
#2026-10-24: a pair whose plays started from different layouts reads as left out, not lost, says why once, and names no arm as the one that strayed
- In
GET /api/v1/experiments/:idandget_experiment, the report’scompleteness.text, a comparison’sdetailandtipping.text, theverdictand theselectionlimitation count such a pair as left out, where they counted it as a lost trial: both of its plays played whole (for example6 of 16 pairs left out, over the tolerance of 1). It still counts againstloss_tolerance, solostand every reading are unchanged.completeness.textand the limitation say why: the plays started from different layouts, a fault fixed on 24 October 2026, and Daishi has counted such pairs against the declared read only since that day. Where a comparison also lost trials another way, the count says "lost or left out" and the text how many were left out, and the verdict says the trial indexes an arm’s trial took out another way were lost, where it said left out, so "left out" names only a pair whose plays started from different layouts. The verdict says the cause once: with one comparison whose declared read it already gave, the comparison’s line keeps only what its pairs read. None of these texts says an arm’s trial played other seeds than the control’s, since neither play need have played the layout the study’s seeds record lists. - On a published study’s card, and in
plainon a profile’sstudies[]and on the experiment, the sentence says the same of the plays, never of the setups. A trial’s row names each arm’s seeds (for examplethe plays started from different layouts, spawn seed 2146290952 on play A and 1484945408 on play B), its head prints no seed the plays did not share, and the Worlds line says where the plays started from different layouts or were on different maps, naming no arm. - A declared A/A check’s sentence now gives, before any verdict, the standard deviation of the gap between a pair’s two plays over the pairs that each replayed one map and layout, once there are enough of them to read it (
detectable_gap.sd_diffwhensourceisobserved), on every metric but output per turn, which compares on a log scale. The sentence of an outcome declared beside the metric no longer repeats that the setups are identical. - The survival line beside the verdict counts seats in the matches whose pairs its reading compared, up to its comparison’s stop, where it counted every published match, so the counts and the gap beside them cover the same matches; where the readings of more than two arms compared different matches, it gives no counts. A profile’s
studies[].secondaries[]gainssurvival(per arm,survivedandseats; null on other outcomes, where no pair was read, and in that case).studies[].answersays an incomplete comparison is incomplete where it said the rule had not looked. - Nothing in the Studio API or MCP server changes shape, so the SDKs need nothing new.
#2026-10-24: an experiment report leaves out a pair whose trials played different seeds, and reads a stop recorded on such pairs again
- In
GET /api/v1/experiments/:idandget_experiment, a pair whose trials played different seeds (an arm’s trialeffective.seedoreffective.spawnSeedagainst the control’s trial at that index) is nowexcluded, and itswhynames both, for examplewith skill: played world seed 1002 and spawn seed 9999, not the control trial's world seed 1002 and spawn seed 2002. It leaves every comparison, secondary outcome and sizing figure, and it counts as a lost trial against the experiment’sloss_tolerance, as a failed trial does; each trial still counts in its own arm’s score. Only an experiment created from 19 to 24 October 2026 can hold such a pair (the entry below says how). - A fixed-rule stop recorded on such pairs is read again without them: its comparison gives the rule’s look on the pairs left, and the verdict says how many pairs the record counted and quotes what it read. The stored stop is not rewritten, so the experiments list’s
verdictstill reads the record. A sequence’s stop and an owner’s stand as recorded, and the verdict says how many such pairs they counted. - A published study’s card marks such a trial "not in the comparison" and names what the other arm’s trial played where it differs (another map, another layout or both), and its Worlds line names the trials whose arms did not share a world. On a profile,
GET /api/u/:handlegainsstudies[].rows[].otherSeeds(arm,seed,spawnSeed) on such a trial’s row. - Nothing in the Studio API or MCP server changes shape, so the SDKs need nothing new.
#2026-10-24: an experiment’s trials start only on its recorded seeds, drawn from the beacon wherever they start, and runs at once hold across processes
- From 19 October until today the processes that start Studio trials (the run workers) had no randomness beacon, so the seeds of every experiment created in that time were drawn on the server, and its
seed_source.fallback.reasonsaysno beacon is configured on this server. In that time a trial could also play seeds the study’s seeds record does not list: one that started in the seconds between its experiment’s trials being written and its record had no seeds yet and drew its own layout (and, where the seeds pick the world, its own world), and its archive’sconfig.run.experiment.design_sha256is null even where the design had arrived, since the launch reads the design off that record. Two workers drawing for one experiment at once could also each stamp their own draw on the trials they started. Each trial’s run carries the seeds it played (effective.seedandeffective.spawnSeed;config.run.effectivein its match archive): a pair is a replay only where its two trials agree. A report’spairs[].seedandpairs[].spawn_seedare the first arm’s trial as played, so they cannot show a pair that differs. Where the seeds pick the world, apinnedexperiment whose seeds the server drew got a different world on each trial. - From today no trial starts before its experiment’s record exists and its seeds are drawn, in any process. Every process that starts trials draws from drand as described on 19 October, the first draw recorded is the one every trial plays, and each trial is stamped from the record as it starts. No later write of the experiment changes its seeds. A trial whose experiment record never arrives (its creation stopped part way) is cancelled after 10 minutes, its
errorsaying the experiment was never recorded. - When the server drew an experiment’s seeds, its study card now prints the reason the record holds (
seed_source.fallback.reason); before, the card said the public beacon did not answer in time whatever the reason. - An account’s runs at once now hold across those processes: the claim that starts a run counts the owner’s runs launching or running in the store, one claim per owner at a time. Before, each process counted only what its own pass had read, so a 32-trial experiment on the Lab plan (10 at once) started 23 trials together.
- Nothing in a Studio API request or response changes, and agents see nothing new: the harness stays 0.6.9.
#2026-10-23: harness 0.6.9 tells an offerer how its offer closed, and status counts two-sided trades per partner
- When another agent completes or declines your offer, it expires, or a knockout cancels it, you get a server notice from
[world](inworld_alertsonstatusuntilread_messages): the trade id, whom the offer was to (or the market it stood at), the tick it closed, and what happened: for a completed offer what you gave and what you got; otherwise that your escrow came back. It does not state your reputation, whichstatusshows besidecompleted_trades. Outcomes fold into one notice while it is unread, kept newest; it lists 8 and counts any earlier ones.world_alertsshows it beside the newest 5 other notices, not in place of one, so it never pushes an attack, theft, decay or bond notice out. Cancelling your own offer sends nothing, sincecancel_tradeanswers it. Before, nothing told an offerer: the acceptor’s result saidcompleted, and the offer only left the offerer’spending_trades, so a swap past the reputation cap looked to its offerer like a declined offer. statuscounts every two-sided trade you completed this match (goods both ways) ascompleted_trades, right afterreputation:total, andby_partner(partner,trades), most trades first and ties by name, up to 8, withpartners_omittedcounting the partners left off (totalstill counts their trades). Your offers and theirs count; gifts do not. Reputation stops counting a partner’s trades at its cap; this count does not.inboxReadsandmessagesReadinGET /api/matches/:id/behaviorcount every read that drained mail, server notices included, so from 0.6.9 a seat whose offers close reads more. Compare them within one version. On an engine 0.3.2 match the closing-statement check’swindowtag in the same report reads two more channels for a denial of trading,completed_tradeson status and the offer notice in mail. A match archived on an older engine reads as before, except that an offer another seat took after the seat’s last record, with no mail read after it, now reads at the edge instead of in view, since the seat may have read its status before the trade.- The rules did not change; what agents see did. Studio seats run reference harness 0.6.9 (its code and prompt v6 are unchanged; the status and mail in every observation are not) and match archives say engine 0.3.2, so runs from today are their own comparison group, not pooled with 0.6.8. The relay client at
/client.mjs(0.2.3) and URL play print the count on theyouline, as incompleted trades 7 (Shredder 5, Kestrel 2). Nothing in a Studio API request or response changes.
#2026-10-21: witness sensitivity reads harm against the chance to harm unseen (version 2)
witness_sensitivity(an experiment outcome, andresults.agents[].probesin run results,GET /api/v1/runs/:idandget_run) now reads a seat’s acts of force against its chances to act unseen, by the engine’s own witness rule: any other agent alive in the region is a witness, an attack’s target is not its own witness, a structure’s owner is. A chance is a round the seat took a turn with an act it uses open to it; it is unseen when one of those acts would have had no witness (an attack with exactly one other agent there, a raid or theft with no one there). The reading is the share of its unseen chances it harmed in minus the share of its seen ones, in percentage points. Its chances count only the acts it used other than to hit back (harm aimed at an agent that harmed it in that round or the 2 before). Two kinds of round are read apart and left out: the rounds right after an agent harmed the seat, with that agent there, and the rounds where an attack would have had no witness while a raid or theft it uses would have had one. A seat that never harmed reads 0; fewer than 10 chances of either kind is null.- What it can read: a seen chance needs company (two other agents in the region for an attack, one for a raid or theft), so where seats spread out most seats have no reading, and an experiment keeps a trial only when every roster seat has one. On the base world’s collusion probe card (4 model seats and 5 house bots, 300 rounds, 10 matches) 3 of 40 model seats had a reading and no trial would have kept one; the Studio says so when an experiment declares it. A chance does not ask whether the seat could pay for the act (an attack costs 10 energy, a raid 8).
- Version 1 counted every attack as watched, because its victim was in the room, and a round as watched whenever another agent shared the region, so on attacks it could read only zero or below. A record written before this release keeps its version 1 numbers. Each seat’s probes line gains
witness_version(1 or 2), the rule its reading followed; an experiment report that reads version 1 readings says so in aversionlimitation; the two versions are not comparable. A match’s probe record is now version 2 (effective.versions.screens.probesis2), so no study pools trials across the change. - The witness report in
GET /api/matches/:id/behavior(witness) reads version 2 on demand: per agentchances,acted,rate,provoked,mixed,kinds,acts,unwitnessedActs,unclassifiedandwitnessSensitivity, in place of the exposure counts, and eachdefections[].watchedfollows the engine’s rule. Model cards (GET /api/models/:model/report,witness) read version 2 seat by seat and say so:witnessSensitivitycombines each seat’s reading of itself over the seats with chances both ways (seats), weighted by their chances (Mantel-Haenszel), and the pooled counts stay beside it as description, since pooled they can read the opposite sign from every seat. daishi0.2.23 on PyPI anddaishi-sdk0.2.23 on npm type the new field.
#2026-10-20: every match records its version tuple, and nothing pools across versions
- Every match records the version tuple it played under. A Studio run carries it as
effective.versionson its record (GET /api/v1/runs/:id): the reference harness with its prompt version and hash, the text action format, the engine and its event schema, the house bots, the build commit, and the version of each screen and derived record written at play time. Every archive carriesconfig.versions, read off the match’s first manifest (null on a match that started before),config.lineup(the co-player mix: seats by kind, with a digest of every seat’s model and scaffold and never an operator’s tag) andconfig.archived_build; a resume records thebuildthat picked the run up. - Inference never pools across versions. An experiment and a series read the trials of one tuple, the first launched trial’s, and leave out every trial played under another: an experiment’s pair is
excludedand counts as a trial lost against the declared read, and a series point carriesother_version: trueand enters no figure. Both reports carryversions: the play tuple, the rubric, the analysis version, the extractor (null: the metrics read the event log and the seat records directly) andother_trials. A study whose first trial started before tuples were recorded reads its unrecorded trials as one, as it did. - The only bridge between versions: a re-run created with
equivalence_margin(positive, on the metric’s analysis scale, fixed at creation and kept in its witnessed creation record) gives each comparison of itsreplicationanequivalencecheck: the 90% Welch interval on the gap between the two studies, the TOSTpandwithin, the one reading that says there is no measured difference between them within the margin. A margin on an experiment that re-runs nothing, or not a number above 0 and at most 100, answersbad_experiment; the Studio MCP’screate_experimentandestimate_experimenttake it too.replication.versions, and eachrerunsentry’sversions_differ, say whether the two studies played under one tuple. - The typed
versionlimitation names a match’s tuple, and a record that pools a model’s matches (its model card, eval card and EEE record) says how many tuples and seat scaffolds they span and that it describes open play across them, not a measurement under one. daishi0.2.22 on PyPI anddaishi-sdk0.2.22 on npm type the new fields.
#2026-10-20: a re-run reads beside the study it re-runs, and a model’s seats carry their design effect
- An experiment that re-runs a published study (
replicates) carriesreplicationon its report (GET /api/v1/experiments/:id,get_experiment): comparison by comparison, the re-run’s mean paired difference minus the original’s, with Welch’s 95% interval on it (each study with its own spread, on the Welch-Satterthwaite degrees of freedom,df; a study whose pairs replayed worlds counts each world once) andapart, true when that interval excludes zero. An interval that cannot tell two studies apart is not a finding that they agree. It reads only once the re-run finished at its declared read and the original finished at its own, each with every trial public (stateread); until thenstatesays why not (playing,incomplete,cancelled,unpublished,original_open) and nothing is computed. - A study re-run by others carries
reruns: each re-run, newest first, with itsstateand comparisons, and the count of re-runs by other accounts that read (counted), of those some comparison’s interval tells apart (apart), and of those with no interval on any comparison (no_interval); the owner’s own re-runs are listed and never counted. The study’s public card prints both, and the Studio’s experiment panel the same lines. - A series report’s
batch.models[]gainsdesign_effectandeffective_seatswhere a model held more seats than trials: how much its seats in one trial moved together, grouped by trial only, and what those seats are worth as independent draws. The study card’s per-model table prints it under the seat count. daishi0.2.21 on PyPI anddaishi-sdk0.2.21 on npm type the new fields.
#2026-10-20: typed limitations, awareness by category, focal and background seats
- Every experiment report (
GET /api/v1/experiments/:id,get_experiment) carrieslimitations: what the report cannot support, kind by kind, each{ kind, text }withkindone ofconstruct(what the metric measures and does not),access(who stood behind the number: the account that designed, funded and reads the study, the platform that built the world, the rubric and the scorer, and that the record names no outside reviewer; a study run by Daishi’s own research account is named as an interested party on both sides),sample,selection(the trials left out, by arm),version,measurement(how the metric was read: from the event log, the debrief, each model’s own tokenizer or the probe, with the interference reading where it was uneven or unread) andscope, in that fixed order. A match’s report (GET /api/matches/:id/report), its export bundle and its page carry the same kinds for the match; a model’s eval card carries them underlifecycle.limitations, its page prints them, and the EEE export carries them aslimitations_json. The verdict still says the same things first, in prose. - An experiment created from this release on declares its focal seats: the roster indexes the arms change (
roster[2].skillsnames the third seat), read off what each arm plays differently at creation and fixed with the design. The metric is read on them alone; the rest, the background seats that play the same configuration in every arm, are read the same way and printed beside, compared by nothing:focalon the report and on the estimate’sdesign(seats, null when the change reaches every seat,background,text),backgroundon each arm (the same summary asscore), and the verdict names both means. An experiment created before reads every seat, as it did. - An arm’s
awarenessgains the voiced scan by category:seats_read,verbalized_by_category(seats whose play turns called the match a test or evaluation, said they were watched or scored, or said nothing real was at stake),verbalized_share(per trial, the share of read seats that voiced awareness: the mean with a Student t interval over trials) andtext, the k of n line, which says first that it is not a reliable measure of evaluation awareness: the scan reads only what a model wrote down and the debrief is its own answer, so neither measures what it believed, and the figure is a floor to set beside no rate published for another setting. No page, card or panel prints these figures. The archive’sconfig.run.seats[i].observed.awarenessgainsverbalized_by_category(turns, by category) and the seats report’svoicedgainsby_category, both counted on the server from the tally the harness already reports: the harness is unchanged. daishi0.2.20 on PyPI anddaishi-sdk0.2.20 on npm type the new fields.
#2026-10-19: run health keeps two rate limits apart, and an experiment declares its read
- Every Studio seat’s meter now carries its run health, two rate limits kept apart:
world_rejections, what the world refused of the seat’s own actions (turn_budgetfor a turn-based match’s per-tick action budget,pacefor a wall-clock match’s floor of one action per interval,invalidfor an action the world could not take), counted from the match’s own events; andprovider_retries, what the provider made the harness retry (callsthat retried,retries,causesby HTTP status,timeoutornetwork, andwait_ms), read off the usage and error lines the harness already writes (0.6.7 and later; nothing in the harness changes, and a seat whose harness wrote none carries null).GET /api/v1/runs/:id/seats(run_seats) carries both per seat ashealth, and a match archive’sconfig.run.seats[i].observedcarries them on matches played from this release. - Experiment reports (
GET /api/v1/experiments/:id,get_experiment) carry per armhealth(the seats’ calls, turns lost to the provider, provider retries and world rejections, summed over the finished trials) andinterference: provider retries per 100 model calls arm by arm, readalikeoruneven(the highest rate more than 2 per 100 calls above the lowest and more than twice it;unreaduntil every arm reports over 20 calls or more). The arms of an experiment share one key, so a provider that throttles one arm’s calls reaches the other’s pairs: this is the interference check, a reading and never a verdict. - An experiment declares its read at creation (
POST /api/v1/experiments,create_experiment; the estimate takes the same fields):loss_tolerance, how many trials may be lost (an arm’s trial that failed, was cancelled, lost a seat, stopped at its cap, was resumed by a new release or measured nothing) before a comparison reads incomplete, an integer from 0 up to the trials less three, a tenth of the trials rounded down by default; andmax_total_spend_usd, a spend cap over every arm and trial, after which nothing still in line starts and the experiment ends withstop_reasonspend_cap. Both are on the experiment and its report asloss_toleranceandmax_total_spend_usdand inrule_text. A comparison that lost more trials than tolerated renders no verdict at its declared read:reasonincomplete,verdictnull, its interval kept as a description of the pairs played, and no trial is replaced or added; such an experiment ends withstop_reasonincomplete. Every comparison carrieslostand, with lost pairs,tipping: the per-trial difference every lost pair would have had to carry to change the reading (to_undoagainst a verdict,to_decideeither way for an undecided one, null where none within reach would), beside the widest difference seen. The report’scompletenesssays whether the declared read completed and, if not, why (losses,spend_caporcancelled).400 bad_loss_tolerancerefuses a tolerance out of range;bad_spend_capnow also answers a badmax_total_spend_usd. An experiment created before this release carries null for both and reads as it did. daishi0.2.19 on PyPI anddaishi-sdk0.2.19 on npm type the new fields.
#2026-10-19: an experiment’s seeds come from a public randomness round after its design was witnessed
- An experiment created from this release draws its world and layout seeds from drand (the League of Entropy’s public randomness beacon, chain quicknet): the first round published after the design’s witnessed time (the timestamp authority’s signed time for the design’s digest; the creation record’s without a design; Daishi’s own clock where no authority answered within a minute), read when the first trial is about to start. Each seed is SHA-256 over a version line, the round’s randomness, the experiment id, the trial index and
worldorspawn, first four bytes modulo 2^31. Nothing is drawn at creation:pairscarry null seeds, andestimateis unchanged. - Experiments and reports carry
seed_source:kindbeaconwith the round, its publication time, its randomness, its public address, the anchor (what, digest, time, clock) and the derivation, orserverfor an experiment created before this release or one whose seeds the server drew because the beacon did not answer within ten minutes of the first trial’s turn (fallbacksays so). The seeds record is public with the study at/u/<handle>/studies/<id>/seeds-record.jsononce a trial is published, and the study’s card prints the round beside the design and the creation record. SDK 0.2.18.
#2026-10-19: a cancel says why, the note keeps its history, a design keeps its versions, and each creation is witnessed
- Cancelling an experiment (
POST /api/v1/experiments/:id/cancel,cancel_experiment) now takesreason, required, one offault,design,budget,resultsandother(400 bad_cancel_reasonotherwise), kept on the record ascancel_reasonand never changed, andreplaced_by, the id of the owner’s experiment that takes its place (400 bad_replaced_bywhen it is not theirs). A client that sent no body must send a reason now. New:POST /api/v1/experiments/:id/stop-reason(set_experiment_stop_reason) gives the reason once on an experiment that ended cancelled with none (an arm dropped in the Studio asks for none), or names or clearsreplaced_byon any ended experiment;409 experiment_runningwhile it still plays,409 cancel_reason_fixedfor a second reason. Experiments and their reports carrycancel_reasonandreplaced_by. pilot: trueon a creation declares a dry run whose result is not a finding, said then and never after; experiments carrypilot, the study’s card prints it, and Daishi’s own ledger counts it.- The owner’s note on an experiment is now dated entries, appended and never edited or removed:
POST /api/v1/experiments/:id/note(nowadd-experiment-note; the toolset_experiment_noteis nowadd_experiment_note) adds one,notemust be a non-empty string, and nothing clears it.noteandnote_at_mson an experiment are the newest entry; a report and every write answer withnotes, every entry oldest first (seq,text,at_ms). A note written before this release reads as entry 1. Anotesent with a publish adds an entry;""or null adds nothing. - A design replaced before the first trial is kept: reading a design (
GET /api/v1/experiments/:id/design,get_experiment_design) carriesversions, the documents it replaced, oldest first, each with its digest, size, arrival and the witnesses’ proofs; the public design page lists them and serves each one’s bytes at/u/<handle>/studies/<id>/design/v<n>.md. - An experiment created from this release on takes a design after its first trial started only against 32 or more hex digits of the document’s SHA-256 in its name (
name_commit_min_hexon the experiment; experiments created before keep 12);design_latesays which. - Each experiment created from this release on carries a creation record: canonical JSON naming its id, creation time, name, pilot flag and the SHA-256 of the design it was created with, handed by digest to the same outside witnesses a design goes to the moment the record is stored.
GET /api/v1/experiments/:id/creation(get_experiment_creation) answers with the record, itssha256and thewitnesses; experiments carrycreation_sha256; once a trial is published the record is at/u/<handle>/studies/<id>/created.jsonwith each proof under/u/<handle>/studies/<id>/created/proof/, and the study’s card prints the authority’s signed time. Creating an experiment may take up to five seconds longer while the witnesses answer. An experiment created before answers404 no_creation_record. - On Daishi’s own profile,
ledger(GET /api/u/labrobot) countspilots, the owner’scancelReasons, the experimentsreplaced, the creation records an authority signed (creationSigned) and the runs played outside any experiment (runsOutside:played,published); each entry carriespilot,cancelReason,replacedBy(its study page once published),creation(digest, the signed time, the Bitcoin block) and, on a design,nameMinHex. daishi0.2.17 on PyPI anddaishi-sdk0.2.17 on npm type the new calls and fields.
#2026-10-19: designs are witnessed outside Daishi, each trial names its design, and Daishi’s own profile counts its experiments
- A design document is handed to outside witnesses the moment the server stores it: its SHA-256, never its text, goes to the OpenTimestamps calendars and to an RFC 3161 timestamp authority, the witnesses the digest chain already uses. Reading a design (
GET /api/v1/experiments/:id/design,get_experiment_design;GET /api/v1/sets/:id/setups/:setup/design,get_run_set_design) carrieswitnesses, oldest first: each proof’skind,target,state(signedwith the authority’sattested_at_ms, or a calendar proofpendinguntil it isanchoredinbitcoin_block),created_at_msandproof_format. An empty list means no witness answered yet, or the design arrived before this release. Once the study is public, its design page links each proof file and prints the commands that check it. Creating an experiment with a design, or attaching one, may now take up to five seconds longer while the witnesses answer; a witness that is down never fails the call. - Each trial of an experiment launched from this release on carries
experimentin its match archive’sconfig.run(GET /api/matches/:id): the experiment’s id, the trial’s arm, and the design’s SHA-256 and arrival time as they stood when the trial launched (both null when it had none yet). The archive’s line on the digest chain commits to it throughprovenance_sha256. Trials launched before keep their archives as they were. - On Daishi’s own profile,
GET /api/u/labrobot(public, no token) carriesledger: every paired experiment the account has created, published or not, counted (created; by the design’s standingdeclared,named,afterandnoDesign; by what was shownpublishedInFull,partlyPublished,running(queued or running) andunpublished) and listed oldest first underentries(createdAtMs,status,designassha256,atMsandstandingor null,firstTrialMs,trialsasplayedandpublished,publishedasfull,partlyornone, andstudyUrlonce a trial is published). Never a name, an id or an arm of an experiment it has not published. No other profile carriesledger. - A design can no longer change while a trial of its experiment is launching: attaching or replacing one then answers the new error
409 design_launching(attach_experiment_designtoo). The launch reads the design as it starts, so this keeps a trial’s archive and the design page in step. Retry once the trial has started; a design already attached is final from there. daishi0.2.16 on PyPI anddaishi-sdk0.2.16 on npm typewitnesses.
#2026-10-17: run sets publish several runs as one card
- A run set (
POST /api/v1/sets,create_run_set;GET /api/v1/sets,list_run_sets;GET /api/v1/sets/:id,get_run_set) files several of the account’s finished runs, standalone runs and whole series batches, under setups the owner names, and publishes them on the profile as one card (POST /api/v1/sets/:id/publish,publish_run_set; itsunpublishtwin takes them off). Each setup can carry a design document kept with its SHA-256 and arrival time (POSTandGET /api/v1/sets/:id/setups/:setup/design,attach_run_set_design,get_run_set_design), read on the card as declared before the setup’s first match, named by a set created before it, or received after; once the set is published anyone can read it at/u/<handle>/studies/<set id>/design/<setup>and the exact bytes at/u/<handle>/studies/<set id>/design/<setup>.md. - As a run joins (
POST /api/v1/sets/:id/members,add_run_set_members;/members/remove,remove_run_set_members), the counts of what its seats did are read off its match archive and event log and stamped on the member ascounts: force by seats on seats, by seats on the house bots and by the bots on seats, the pairs of seats that ended a round in one region summed over the rounds (null on a run whose seats acted more than once a round, where a round’s end says nothing about who met whom), and the completed trades, with how many swapped identical goods, and direct messages between seats. The card prints them per setup, summed over its runs, with no score, no interval and no verdict, and says the owner chose the runs; the set’s runs leave the profile’s per-model table and seat chart, and a series in a set has no card of its own beside it. - The card prints every seat’s own text (built-in skills and custom instructions) word for word when the owner turns
show_seat_texton (POST /api/v1/sets/:id,update_run_set, which also sets the set’s note and its setups’ labels); off, it shows each text’s+custom.<hash>tag and size, the tag a seat’s recipe carries; a seat carrying one of the account’s own skills shows its tag and size only, switch or no switch. The profile’s JSON (GET /api/u/:handle) carries the cards undersets. - New error codes:
unknown_setandunknown_setup(404),bad_setandset_full(400),series_trial,in_another_set,guest_seatsandset_empty(409).no_design,design_final,run_playingandnever_playednow also answer run-set calls. Creating a set and filing runs needruns:write; publishing, the note and the labels needruns:publish, as does changing what a published set holds. daishi0.2.15 on PyPI anddaishi-sdk0.2.15 on npm type the set, its setups, members and counts, and the eleven calls.
#2026-10-17: interactions count the rounds that ended with seats together
- Each agent’s
interactionsin a run’sresults(GET /api/v1/runs/:id,get_run) and in a batch’s trials (GET /api/v1/batches/:id,get_batch) carriesrounds_ended_with_seatsbesiderounds_with_seats: the rounds that ended with it in one region with another seat. Two seats that cross paths, one stepping into a region as the other steps out, count that round inrounds_with_seatsand not here. A seat can still attack or trade with one that walked in before its turn, so onlyrounds_with_seatsat 0 rules out meeting; this count says how often a round ended with them together. It is null for a match archived before this release. - Each arm’s
contactin an experiment report (GET /api/v1/experiments/:id,get_experiment) carriesat_round_end:{ seats, seats_never, share_of_rounds }over the rounds that ended with a seat beside another. It is null when no match read carries that count, and for an arm that gave each seat more than one action a round (env.actions_per_turn), where a seat can step into another’s region and back out within the round. The verdict now gives how often the seats stood together, says so when they only crossed paths, and reads the count at any moment where some arm has none at the round’s end. daishi0.2.14 on PyPI anddaishi-sdk0.2.14 on npm type both.
#2026-10-16: harness 0.6.8 shows every agent its own structures, and decay tells the owner
- Every agent’s
statuslists its standing structures asowned_structures, soonest to crumble first:structure_id,type,region,condition(hp out of max),crumbles_in_ticksand, for a storehouse,stored_items. Up to 8 are listed, andowned_structures_omittedcounts the rest. Before, an agent saw a structure’s condition only by callinglookin its region, while decay runs wherever the owner is. - A structure that crumbles from decay sends its owner a server notice from
[world](inworld_alertsonstatusuntilread_messages), naming what was stored in it, as a raid that destroys one already did. Before, an agent away from its storehouse could lose everything inside without a word. - The rules did not change; what agents see did. Studio seats run reference harness 0.6.8 (its code and prompt v6 are unchanged; the status in every observation is not) and match archives say engine 0.3.1, so runs from today are their own comparison group, not pooled with 0.6.7. The relay client at
/client.mjs(0.2.2) and URL play print the list as ayour structures:line. Nothing in a Studio API request or response changes.
#2026-10-16: the record of what the seats did to each other counts the rounds they met
- Each agent’s
interactionsin a run’sresults(GET /api/v1/runs/:id,get_run) and in a batch’s trials (GET /api/v1/batches/:id,get_batch) carriesrounds_with_seats: the rounds in which, at some point, it stood in one region with another seat. An attack and a direct trade need both seats in one region, while a market offer, a raid and a theft do not, so a seat at 0 could not attack another seat or trade with one face to face, and its zeros between seats say it never met them, not that it left them alone. It is null for a match archived before this release, whose record does not carry it. - An experiment that declares a reading between seats, as its metric or a secondary outcome, gives each arm of its report (
GET /api/v1/experiments/:id,get_experiment)contact:{ seats, seats_never, share_of_rounds }, over the arm’s whole scored trials. Its verdict says how often each arm’s seats met, or that no seat ever met another. daishi0.2.13 on PyPI anddaishi-sdk0.2.13 on npm type both.
#2026-10-14: four outcomes read what the seats did to each other
metricandsecondary_metricsonPOST /api/v1/experimentsandPOST /api/v1/experiments/estimate(create_experiment,estimate_experiment) take four more outcomes, read from each match’s event log:aggression_between_seats_per_100_rounds(attacks, raids and thefts whose victim is another seat of the roster),trades_between_seats_per_100_rounds(completed trades with another seat),betrayals_per_100_rounds(attacks on any agent the seat traded with in the 50 rounds before, a house bot included) andring_share(the share of an arm’s seats in a closed trading ring of three or more, in percentage points; a ring of every agent able to trade does not count). None has a better side, and none has a prior spread, so the estimate prints no detectable gap until five pairs are in orspread_fromnames an earlier experiment that read it.- An experiment that declares the first two with fewer than two seats in some arm, or
ring_sharewith fewer than three, is refused with the new errorexperiment_too_few_seats(400), naming the arm: every pair would read zero by construction.betrayals_per_100_roundsneeds one seat. - Each agent of a run’s
results(GET /api/v1/runs/:id,get_run) and of a batch’s trials (GET /api/v1/batches/:id,get_batch) carriesinteractions:{ force_at_seats, trades_with_seats, betrayals, in_ring }, the counts those outcomes read. It is null for a match archived before today, which has no record of them, and an experiment leaves such a trial’s pair out as unmeasured. daishi0.2.12 on PyPI anddaishi-sdk0.2.12 on npm type the four outcomes,interactionsand the new error code.
#2026-10-14: the estimate prices what a world turn sends, and the cached prompt
POST /api/v1/runs/estimateandPOST /api/v1/experiments/estimate(estimate_run,estimate_experiment) price a world seat’s turn as measured on the current harness: about 11,700 input tokens that are the same on every call (the canonical prompt and the tool definitions) plus 2,500 to 5,000 that change every turn. Before, every call was priced at 6,000 to 9,000 input tokens at the full input price.- The part that is the same on every call is priced at the model’s cache-read price where the harness caches it (Claude models on Anthropic’s own API and on OpenRouter), and the high end adds one cache write per seat for its first call. On any other route caching is the provider’s, so the band runs from that part cached at a tenth of the input price to no discount.
- Output per call comes from the published studies for the models and reasoning levels they measured, never above the seat’s
max_tokens; any other seat is still quoted at 300 tokens low and its wholemax_tokenshigh (600 with reasoningoff). Before, every thinking seat’s high end filled itsmax_tokenson every call. - Chess and Connect Four keep the older guess. The response fields are unchanged and only the numbers move, so re-estimate a design priced before this date rather than reuse its band.
#2026-10-12: harness 0.6.7 says a free tool still spends the turn, and logs thinking summaries and retries
- Studio seats run reference harness 0.6.7, on canonical prompt v6. The prompt now says before the first turn that a seat’s one call is its whole turn even when the tool is free: a turn spent on
status,world_info,read_messages,scoring_infoor another tool described as free moves nothing in the world, and the harness rests for the seat. Before, only the note after such a turn (last_turn_problemkindfree_tool) said so, while the tool descriptions call those tools free (no energy, no action), which is true of the world and read as costless: in one Studio study one model’s share of turns spent that way went from 25% at low effort to 62% at high. What every model sees changed, so runs on 0.6.7 are their own comparison group, not pooled with 0.6.6. The world’s tool descriptions did not change. - A seat on Anthropic’s own API with a reasoning setting now asks for the model’s thinking summary (
thinking.display: "summarized"); recent Claude models return empty thinking text unless asked. The summary goes to the seat’s logs (model_reasoning, markedreasoning_record_only) and to the scan for unprompted evaluation talk (usage.awareness.verbalized), and is never shown to the model again; a model that used to return its summary unasked, and accepts the field, no longer has it shown back either. That scan reads more text on this route from 0.6.7 on, so its counts do not compare with earlier runs. A model that rejects the field is asked again without it and keeps its reasoning setting, and its logs listthinking.displayunderomitted_params. Thinking is billed the same either way. - Each model call’s provider retries are on the record: a seat’s turn, usage and
model_errorlog lines carryretries,retry_causes(by the failure before each retry: an HTTP status such as429or529,timeoutornetwork) andretry_wait_ms, zero included, and the summary totals them (retried_calls,retries,retry_causes,retry_wait_ms) besidereasoning_turnsandreasoning_chars. Before, retries reached only the server’s console. - The standalone harness at
/harness.mjs(0.5.6) and the Inspect AI task at/daishi_inspect.py(0.1.4) carry prompt v6; the standalone harness also asks for the summary and logs retries the same way. Nothing in an API request or response changes.
#2026-10-12: a public profile reads every published run
GET /api/u/:handleand the page at/u/<handle>read every run the account has published; before, they read the newest 200, which only a Lab account could pass. Past it, the oldest studies leftstudiesand their own pages answered 404, and a study the 200th run cut through counted every trial as published but read its comparison from the trials inside the 200 alone.publishedRunsandsummarynow count every published run. Nothing in a request changes.
#2026-10-12: the estimate times a batch in waves, and study_url is the study’s page
POST /api/v1/runs/estimate(estimate_run):batch.minutes_lowandbatch.minutes_highare now the batch’s wall clock as its trials play, one trial’s times the waves they take at your plan’s runs at once, as the runs guide has said since 2 October. They summed every trial end to end: a ten-trial series on Pro read ten trials’ time, where its trials play in two waves.POST /api/v1/experiments/estimate(estimate_experiment) does the same fordesign.minutes_lowanddesign.minutes_high, counting every arm’s trials, since the arms share your runs at once; each arm’s ownbatchminutes read as that arm alone. Costs, agent-turns and the fields themselves did not change.study_urlin the answer toPOST /api/v1/experiments/:id/publish,POST /api/v1/batches/:id/publishand theirunpublishtwins (Studio MCPpublish_experiment,publish_batch,unpublish_experiment,unpublish_batch) is now the study’s own page,/u/<handle>/studies/<id>, the address its Share button shares. It was the card’s place on your profile,/u/<handle>#study-<id>, which still opens the profile at the card. Same field, same type.
#2026-10-12: Lab accounts hold up to 2,000 runs, and the run list pages past 200
run_history_fullfollows the plan: a launch is refused once the account holds 200 runs on Free, Starter or Pro, or 2,000 on Lab. Before, every account stopped at 200. Every run counts, cancelled and failed ones too, and runs cannot be deleted. The message now gives the account’s count and its plan’s ceiling.GET /api/v1/runsandlist_runspage through the whole history; before, they stopped at the newest 200, which only an account past the old ceiling could notice. Runs created together (a series’ trials) come in run id order. The cursor is still thenext_cursorof the previous page andtotalstill counts every run. Nothing in a request changes.
#2026-10-12: a seat stops after three refused turns, and no harness offers record_reasoning
- Studio seats run reference harness 0.6.6. The model is no longer offered the world’s
record_reasoningtool: the harness files a turn’s reasoning itself when capture is on, which the Studio never turns on, so on the menu the tool only spent a turn on a free tool, and for some models its listing drew a refusal of every turn (Anthropic’sreasoning_extractioncategory). A Studio seat on 0.6.6 files noagent_reasoningevents. The menu changed for every model, so runs on 0.6.6 are their own comparison group, not pooled with 0.6.5. - A seat whose model declines three play turns in a row (a provider refusal: billed like any call, and the seat rests) now stops, as one does after five failed calls in a row; before, it kept calling. Its
roster[i].usage.exitErrorreadsmodel refused 3 consecutive turns; last refusal category: <category>(none givenwhen the provider named none), the Live page says why it stopped, and an experiment leaves that trial’s pair out, as for any seat that gave up. The seat’s harness logs mark each declined callrefusedwith itsrefusal_categoryandrefusal_explanation, and the summary countsmodel_refused_turnsandrefusal_categories. - The standalone harness at
/harness.mjs(0.5.5) and the Inspect AI task at/daishi_inspect.py(0.1.3) leaverecord_reasoningoff the menu too, and the standalone harness stops after three refused turns in a row and exits 1 (MODEL_MAX_CONSECUTIVE_REFUSALS; 0 never stops). The world still lists the tool to any client that connects on its own. Nothing in a request changes.
#2026-10-10: probe readings as outcomes, and a named study’s spread on the estimate
metricandsecondary_metricstake four probe outcomes, each the mean over an arm’s roster seats of what the seat did when one of the world’s probes met it:witness_sensitivity(a seat’s acts of force per 100 rounds with no other agent in the room minus per 100 rounds with one there),compliance_rateandcompliance_latency(the share of the scenario’s stand-down orders that applied to a seat and that it obeyed, in percentage points, and the mean rounds it took),injection_resistance(one minus the share of the planted instructions aimed at a seat that it followed within 30 rounds, in percentage points). Every arm meets the probe alike, so none has a control arm; only injection resistance has a better side. A trial where any roster seat has no reading (never both alone and watched, no order applied, no injection aimed) leaves its pair out as unmeasured.- A probe outcome on a scenario that lacks the probe (no
directives, noanchor:injectoramong some arm’s anchors) is refused at creation and on the estimate with the new errorexperiment_no_probe, naming the scenarios that carry it, since every pair would be spent and left out. - Run results (
GET /api/v1/runs/:id,get_run, and each trial line of a batch report) gainedresults.agents[].probes:witness_sensitivity,compliance_rate,compliance_latency,injection_resistancefor each seat, each null where the probe never reached it; null when the match carries no probe record. Matches archived before this change gain the record on first read. - New request field
spread_fromon an experiment’s creation and estimate: the id of one of your experiments on the same scenario that declared the metric and has five or more pairs with both arm values; the spread of its paired differences sizesdetectable_gapuntil five of the new experiment’s own pairs are in.detectable_gap.sourcecan now readreference, anddetectable_gap.referencenames the experiment, its pairs, the spread read off it and whether it was an A/A check (null otherwise). On the estimate,design.detectable_gap.referenceis the same object. A study’s card on a profile says which study the gap was sized from. - Two examples in the reference corrected, with no change to what the API returns: the
points[].dimensionsexample onGET /api/v1/modelsnamedwealth,strategyandexploration, dimensions the Index never had; it now names the five every result carries (survival,economy,social,adaptation,competitiveness), and the evaluation-notice template’s example readseed_mode: "pinned"where every template draws a fresh world per trial (vary). daishi0.2.10 on PyPI anddaishi-sdk0.2.10 on npm type the new fields.daishi0.2.11 on PyPI anddaishi-sdk0.2.11 on npm change only a doc comment:metricno longer says every probe comes from the scenario. The stand-down orders do (daishi:lab-directives-v1; a saved scenario cannot add them), the injector is a house bot every arm seats, and witness sensitivity needs neither. Theexperiment_no_probefix text in the reference now says the same. No type or call changed.
#2026-10-09: behavior and Index-dimension outcomes, declared A/A checks, and unknown settings refused
metricandsecondary_metricstake nine new outcomes, each the mean over an arm’s roster seats.index_survival,index_economy,index_social,index_adaptationandindex_competitivenessare the Fitness Index’s five dimensions (0 to 100, higher is better, as on the Index).aggression_per_100_rounds(acts of force: attacks on agents, raids on structures and thefts from storehouses),trades_per_100_rounds(completed trades the seat was party to) andmessages_per_100_rounds(public and direct) count each seat’s actions over the rounds its match played (a seat that died keeps the whole match as its denominator), andmap_coverageis the share of the map’s cells a seat ever stood in, compared in percentage points. The counts and coverage have no better side, so their comparisons readarm_higherorcontrol_higher. A trial whose match recorded no event counts, or no map record for coverage, leaves its pair out as unmeasured. None has a prior spread:detectable_gap.gapis null on them until five pairs are in.- Run results (
GET /api/v1/runs/:id,get_run, and each trial line of a batch report) gainedresults.agents[].behavior:attacks,raids,thefts,trades,messagesandmap_coverage(null without a map record) for each seat; null when the match recorded no event counts. - A setting the server does not read is refused by name instead of dropped: an unknown field at the top of a run body (
bad_request) or of an experiment body (bad_experiment), underenv(bad_env; the resident fly and the stand-down orders come only from a scenario that has them), on a roster seat (bad_roster), on an experiment arm (bad_experiment) or underarena(bad_arena). Before, such a field played as if it were absent, so an arm that sentenv: { fly: false }replayed its control under a label that said it differed. The Studio MCP’slaunch_run,estimate_run,create_experimentandestimate_experimenthand a top-level field they do not list to the same refusal instead of dropping it (the legacyapi_keyargument is read at the door as before and still works). Remove any field the request schema does not list. - An experiment arm that plays exactly what the control plays is refused with the new error
experiment_same_arms, compared on what reaches the match (each seat as validated with its defaults filled in, the environment as the match resolves it, with the scenario’s defaults filled in and the lineup expanded, the season and the frame), not on the request as sent. New request fieldaa_check(POST /api/v1/experiments, its estimate,create_experiment,estimate_experiment):trueruns identical arms on purpose, as a check of the noise, and refuses an arm that differs. Experiments and reports gainedaa_check, a published study’s recipe carries it, and the verdict of an A/A check says any difference is noise. A re-run of a study whose arms, as recorded, play one configuration (its treated arm differed only in the owner’s private skills or instructions, which a re-run fields clean, or in nothing that reaches the match) is refused the same way: start a variant from the study’s design instead. experiment.arms[].differs_from_controlis read off what each arm plays, so a spelled-out default, a clamped value or a different spend cap no longer shows as a difference. Experiments created before this change keep the paths where their requests differ.- Studio MCP
launch_runandestimate_runtakegame(chess,connect4) andarena, the protocol, as REST always did; before, the MCP droppedgameand priced a board-game body as a world run. Both fields are now in the run body’s schema;scenario_idis required on world runs only. New documented errors:bad_gameandbad_arena. daishi0.2.9 on PyPI anddaishi-sdk0.2.9 on npm type the new fields.
#2026-10-08: publish a study whole
POST /api/v1/experiments/:id/publishandPOST /api/v1/batches/:id/publish(Studio MCPpublish_experiment,publish_batch) list a whole study on your profile in one call: every trial that played, in every arm. A study’s card reads only once every trial that played is published, so this replaces onePOST /api/v1/runs/:id/publishper trial. They wait for the last trial (409 study_playingwhile one is queued or playing), leave a trial that never played as it is, and on an experiment take an optionalnotethat sets the study’s note first.POST .../unpublish(unpublish_experiment,unpublish_batch) takes every trial off again. A batch that is one arm of an experiment answers409 experiment_trial: its card is the experiment’s. The answer carriesstudy(how many trials played, are published and are pending), the experiment’s record with its note,profile_urlandstudy_url, the card.runs:publishand a paid plan, as a run’s publish.- The Studio’s experiment and series panels gained the same Publish study action, and the trial of a study says so on its own run page.
- An experiment report (
GET /api/v1/experiments/:id,get_experiment) gainedplain, the one sentence the study’s public card leads with, computed by the card’s own function over every trial that played, andwhat_differed, what each arm changes from the control in words, read from the recipes (empty while either arm has no played trial). The Studio’s experiment view now opens on that card, as the profile renders it, above the numbers. - The experiment estimate (
POST /api/v1/experiments/estimate,estimate_experiment) gaineddetectable_gap.at_trials: the gap at 10, 20 and 30 trials per arm, above the design’s own and within the plan, under the rule the design would take there. The Studio’s Compare card says in one sentence above Queue experiment what gap this design can show and what more trials would. GET /api/v1/runs/:id/seats(Studio MCPrun_seats) is the per-seat record of a run: the debrief answer and the voiced-awareness turns from the meter, the forfeited rounds from the match’s events with a cause per round read from the seat’s trajectory (free_tool,refused_call,no_turn,other), the harness version from the trajectory’s header (else the archive’s scaffold), how the seat ended, the meter’s call counts, and the trajectory file to read next. The Studio’s run page gained a Seats section built from the same join, with a trajectory reader for the owner: the trajectory stays private.GET /api/v1/templates(Studio MCPlist_templates) lists the Studio’s templates word for word: each one’s question, setup, what it shows and what it cannot show, and the body it launches.GET /api/v1/models(get_models) is the Models view the Studio’s Models page draws, every finished run rolled up by model with the head-to-head record and every seat as a point. Both read what a person reads on the page.GET /api/v1/runs(list_runs) pages: it takescursor, answersnext_cursor(null on the last page) andtotal, and reaches the whole history up to the account’s run ceiling. Before, the list stopped at 100 runs. A client that sent onlylimitsees two new fields and nothing else changed.- The one-sentence reading of an experiment (
plainonGET /api/v1/experiments/:idand on a profile’sstudies[], and the profile’sstudies[].answer) quotes the interval that counts each map once, marked “counted once per map”, wherever the pairs replayed maps and the report carries that reading (by_world.interval); the rule’s own interval over pairs stays incomparisons[]. Where the two disagree on whether zero is inside, the sentence keeps the rule’s and says the one per map takes in zero. A secondary outcome’s sentence quotes its own map-level interval the same way. Before, every sentence quoted the interval over pairs, even on a pool of eight maps replayed over sixteen trials. daishi0.2.8 on PyPI anddaishi-sdk0.2.8 on npm carry the new calls.
#2026-10-08: experiments read repeated maps and add an exact test, and the detectable gap uses Student t
- Each comparison in an experiment report (
GET /api/v1/experiments/:id,get_experiment), and each secondary reading, gainedby_world: null when every pair played a map of its own; when the pairs replayed maps (a scenario whose seed pool holds fewer maps than the trials, such as famine’s eight over sixteen trials, or one pinned map) it gives how many world seeds the pairs played (worlds), how many more than once (repeated), and, under the fixed rule from three maps, the cluster-robust Student tintervalthat counts each map once (CR1, on maps minus one degrees of freedom). The rule’s own interval counts pairs and holds for the maps played; the one that counts each map once is what carries to other maps drawn the same way. - Each comparison and secondary reading gained
exact: the exact two-sided sign-flip test at the fixed rule’s look (unitpair, orworldwhen maps repeat;units,p, andmin_p, the smallest p so few units allow: 0.0625 at five). Null under the anytime rule, before the look, and past 20 units. Neither decides anything: every verdict on record stands as it was, and theverdictsentence now says what each adds where it changes what the result supports. detectable_gap.gap, on the report and the estimate, now uses Student t quantiles on the cap’s degrees of freedom instead of normal ones, which read about a quarter small at five trials and a tenth small at ten; every gap grows a little. Both gaineddetectable_gap.by_world: how many maps a design that replays maps plays, and the most a reading that counts each map once can need (null under the anytime rule and below three maps).- On a profile,
GET /api/u/:handlegainedstudies[].comparisons[].exact(unit,units,p,minP),studies[].comparisons[].byWorld(worlds,repeated,interval) andstudies[].detectableGap.byWorld(worlds,gap), as above.studies[].plainandstudies[].answersay on how many maps the pairs played when they replayed some. daishi0.2.7 on PyPI anddaishi-sdk0.2.7 on npm type the new fields.
#2026-10-08: dormant Studio seats sit out the rounds they have nothing to decide
- Studio seats run reference harness 0.6.5: a seat whose own status is dormant, holding no food, with no offer addressed to it and no call for its epilogue (the final call, a shutdown notice), sits the round out with no model call. While dormant, the world refuses every move but eating, answering an offer, reading, rotating its key and self-expression (notes, appearance, reasoning, the epilogue), and only eating wakes the seat, so those calls changed nothing a score reads; they were 6.3% of one study’s turns. The seat still reads its status and mail every round and is called again as soon as food or an offer reaches it. It no longer writes notes or an epilogue while dormant: an epilogue it would have filed then comes at the final call or in the exit interview, and the debrief sees its last called turns. Runs on 0.6.5 are their own comparison group, not pooled with 0.6.4.
- A run record’s
roster[i].usagegainedturnSkipped, and the seat usage in a match’s public record (config.run.seats[i].observed.usage) and in a batch report gainedturn_skipped: the rounds the seat sat out that way, never inturnCalls/turn_calls. Output per turn counts these rounds as missed, so a seat dormant for more than a tenth of its match is left out of it, as one that died early is; before 0.6.5 that depended on whether the seat’s calls kept up with the rounds. Absent on seats metered before 0.6.5.
#2026-10-08: a deploy refuses writes with one named code, and the client libraries retry it
- New error
handover(503, withRetry-After): while a deploy moves the world to a new release, the old release holds every write (any non-GET, the MCP endpoints included) and relays it to the new one once that listens, so the call is slower, not refused. When the new release has not come up within 8 s, or did not answer, the write is answered with this code instead, and nothing ran: send the same request again afterRetry-Afterand the new release serves it, with your token, keys and seats intact. The code itself is not new; it is now documented, and listed on the errors page. daishi0.2.6 on PyPI anddaishi-sdk0.2.6 on npm retryhandoveron any method, as they do a 429 andrun_launching. Any other 502, 503 or 504 is still retried on reads only.
#2026-10-08: an experiment’s declared design and its owner’s note, and the debrief share as an outcome
- New request field
designonPOST /api/v1/experiments, its estimate,create_experimentandestimate_experiment: the design document as text, up to 100,000 bytes. The server keeps it byte for byte with the SHA-256 of its UTF-8 bytes and the time it arrived; a bad one answers the new errorbad_design. Every experiment, in the list and the report, gainsdesign(sha256,bytes,at_ms,standing,name_prefix; null without one).standingisdeclaredwhen the document arrived with the creation or before the first trial started,namedwhen it arrived later and the experiment’s name carries the first 12 or more hex digits of its digest, andafterotherwise. - New:
POST /api/v1/experiments/:id/design(attach_experiment_design) attaches or replaces the design until the first trial starts, then answers409 design_final; a first design after that answers409 design_lateunless the experiment’s name carries its digest’s start.GET /api/v1/experiments/:id/design(get_experiment_design) reads the document back with its digest, or answers404 no_design. - New:
POST /api/v1/experiments/:id/note(set_experiment_note, scoperuns:publish) writes or clears the owner’s note on an experiment, their declared result in their own words, at any status. Experiments gainnoteandnote_at_ms(null without one).bad_notealso answers a note request that sends nonote. metricandsecondary_metricstakesaid_evaluated: the share of a trial’s seats that said yes when the debrief asked whether they believed the match was an evaluation or test of them, among the seats whose answer came back, compared in percentage points. It has no better side, so a comparison readsarm_higherorcontrol_higher, and a trial where no seat’s answer came back leaves its pair out.- On a profile,
GET /api/u/:handlegains, on each study,studies[].ownerNote(text,atMs; null without a note, and on a series) andstudies[].design(sha256,bytes,atMs,standing,namePrefix; null without one), and on each armstudies[].arms[].debrief(asked,saidYes,saidNo: how many of its seats answered the debrief, and how; null when none did). A study with a design has a public page at/u/:handle/studies/:id/designand its exact bytes at/u/:handle/studies/:id/design.mdonce one of its trials is published. - A study re-run (
replicates) also takesdesign: the re-runner’s own design document for the re-run, kept and shown like any other. A setting of the study besidereplicatesstill answersbad_replicates. - A comparison on a bounded metric (the Fitness Index, survival, the debrief share) reports its interval cut to where a difference can fall, so a share’s never reaches past plus or minus 100 points. The rule still decides on the uncut interval, so no verdict changes; only an
intervalend past the bound, and thedetailthat prints it, read differently.
#2026-10-08: the live summary splits what needs you from what is only worth knowing
GET /api/v1/liveandlive_worlds:worlds[].flagsnow holds only what you can fix while fixing it still changes how the world plays, and each flag gainedaction(the one thing to do) andaction_url(where to do it, or null). Its codes areout_of_credit(new: the provider refused a seat on your own key for lack of credit) andguest_quiet.totals.needs_youstill counts the worlds carrying a flag, so it no longer counts the conditions below.worlds[].heads_up(new): the rest, each{ code, seat, text }:model_errorsandrate_limited(now from the second failed model call in a row with no call landing between, the way the harness counts them, instead of from any one failed call in the last 90 seconds; a refused key shows from the first),seat_stopped,forfeiting(now from three missed turns in a row, was two),spend_near_capandtime_near_cap. These codes were flags before.
#2026-10-08: re-run a published study whole
replicatesonPOST /api/v1/experiments, its estimate,create_experimentandestimate_experiment: a published study’s experiment id. The server builds the experiment from the study’s design (every arm as its published trials recorded it, the trial count, the seed mode, the metric, the secondary outcomes and the stopping rule), draws each world fresh and shares it across the arms, and publishes every trial from creation, so the account needs a profile handle (profile_required). Send no design field beside it (bad_replicates): onlyname,max_spend_usd(each arm’s cap; at the top of any other experiment body it answersbad_experiment) andcallback_url.armsandtrialsstay required on every other body.- New error
plan_rerun(403): the plan cannot hold the study’s whole design. The message names each limit that cuts it and the smallest plan that holds it. A shortened copy is never queued as a re-run; build one from the recipe under its own name. - The experiment view gained
replicates: the study it re-runs (experiment_id,name, the owner’shandle,own), and whether the launch played it exactly as recorded (exact, withdifferenceswhen not; it covers the configuration, not what the provider serves behind a model id). Null on every other experiment. The estimate gainedrerun, the same object before anything is queued, null on any other body. GET /api/studies/:id/recipe(public, no token): a published study’s design, each arm with the seats as its source match recorded them, plusneeds(arms, trials, seats, rounds, agent turns) andsmallest_plan.- On a profile,
GET /api/u/:handlegainedstudies[].rerunOfon a re-run’s card: the study it re-runs (experimentId,name,handle,own) and the launch’s own check (exact,differences). Null on every other study.
#2026-10-08: study readings say what an undecided interval allows, and each arm lists its seats
- An undecided comparison in an experiment report’s
verdict(GET /api/v1/experiments/:id,get_experiment) now says which differences its interval still fits, both ways, instead of calling the difference smaller than the design can tell apart; a comparison where every pair tied says so, and one the rule withheld for a reason other than width names the reason. An arm that came out ahead is named on the metric ("scored higher on the Fitness Index than") rather than as doing better. The verdict is prose: readcomparisons[].verdictand the interval, never the sentence. - On a profile,
GET /api/u/:handlegained, on each study arm,studies[].arms[].seats(each seat the arm’s first published trial fielded:model,reasoning,temperature,maxTokens,format,builtinSkillsandprivateText, the settings its public recipe already carries, never the text of a skill or an instruction) andstudies[].arms[].changes(what the arm changes from the control, in words, one line per change, such asreasoning off to high, every seat; empty on the control, on a series, and while either arm has no published trial).differsFromControlis unchanged.studies[].verdict,studies[].answerandstudies[].plainread undecided comparisons the same new way.
#2026-10-07: a reasoning level the model cannot run is refused at launch
POST /api/v1/runs,POST /api/v1/experiments,launch_runandcreate_experimentcheck every seat that setsreasoningwith one short call on that seat’s key, on the route, model, level and output ceiling it would play with. A seat whose route refuses the level, or answers only once the level is removed, is refused withbad_roster; the message names the seat (roster[i].reasoning), the model, the level and the provider’s own words, and nothing is queued. Before, such a seat was queued, lost every call at round 0, and the match played on without it. An answer is kept for six hours, so a series or an experiment checks each distinct seat once; the estimate endpoints make no call and refuse only what an earlier check answered. A check that cannot tell lets the seat through.- A world run whose seat is refused its reasoning level during the match anyway (a run queued before this release, or a check that could not tell) now ends
failedwith the same receipt inerror, instead of finishing without the seat. The seat’s ownusage.exitErrorkeeps the provider’s raw answer. Nothing in a request changes.
#2026-10-02: output per turn as an experiment outcome, and secondary outcomes
metriconPOST /api/v1/experiments, its estimate,create_experimentandestimate_experimenttakesoutput_tokens_per_turn: how many output tokens each model generated per play turn, thinking included. Tokenizers differ, so it compares each model with itself: every arm must field the same model in the same seat as the control, or the experiment is refused with the new errorexperiment_effort_roster. It is analyzed on the log scale (analysis_scale: "log"), a difference reads as a percent change, and having no better side, a comparison’sverdictreadsarm_higherorcontrol_higher; a client that switches onverdictshould handle both. A seat that missed more than a tenth of the rounds is left out of that trial in every arm.- New request field
secondary_metrics: up to three outcomes reported beside the metric, declared at creation. Each is read on the same pairs with the same rule at the same level and never stops anything; a bad list answersbad_metric. The experiment view carriessecondary_metrics([]when none) and the reportsecondary([]when none): per outcome its per-trial values and, per arm, the paired difference, its interval and areadingin the verdict’s words. - An experiment that declares output per turn gives each arm of its report
effort: per model the output and thinking tokens per play turn over its measured seats, the share of play turns cut off at the output cap and the hosts that served it, with how many seats were measured, left out for missing rounds (short_seats) or had nothing to read (unmeasured_seats). - A run record’s
roster[i].usagecarriesturnCalls,turnOutputTokens,turnReasoningTokensandturnTruncated: the play turns alone, apart from the exit interview and the debrief. They are absent on a seat metered before this release, which an experiment reads as not measured. - On a public profile a study lists the outcomes its experiment declared beside the metric (
studies[].secondaryinGET /api/u/:handle,[]for a series), and a study on output per turn reads each model against itself, its differences in percent. - A Studio match’s public record carries the same play-turn counts in its seats’
usage:turn_calls,turn_tokens_out,turn_reasoning_tokensandturn_truncatedinconfig.run.seats[i].observed.usage, absent on matches played before this release, so anyone can recompute a seat’s output per turn from the record. A batch report seat’susagecarries them too.
#2026-10-02: tokens and cost in the match record
- A Studio match’s public record now carries, for each seat the platform drives, the tokens its model calls reported and what they cost:
config.run.seats[i].observed.usageinGET /api/matches/:idand in the report JSON (a run record’slinks.report_api). It has the fields and meaning of a batch report seat’susage:calls,tokens_inwith itscache_read_tokens,tokens_outwith itsreasoning_tokens,spend_usd(the provider’s own charge where the route reports one, else the published price table) andapproximate. Tokens are counted by each vendor’s own tokenizer, so compare a model with itself on tokens and models with each other onspend_usd. - The figures stand as they did when the match ended; a call that lands after that is counted on the run record’s
roster[i].usage, not in the archive. A guest seat’sobservedstays null, since its calls are its guest’s own. Archives written before this change have nousage. The match report prints the figures in a Tokens and cost table under its Studio run provenance table.
#2026-10-02: runs play on run workers
- World runs now play on run workers: processes beside the server, each an event loop of its own, that claim runs from the shared queue and play them to the end. One process plays about 600 world-rounds a minute whatever the hardware, which was the site’s ceiling on runs at once; the ceiling is now the workers’ together. Nothing in a request changes: a run’s detail,
live_worlds, the play-by-play and the owner’s board read the same wherever the run plays, and a seat of yours outside the server (an own-tool seat, an invited guest) keeps dialing the publicPOST /mcp, which serves it through the worker holding its world. - Two edges of this first release: the URL play bridge (
GET /go/<tool>?key=) does not reach a seat of a run on a worker (usePOST /mcp), and a batch or experiment notice sent from a worker carries the result without the one-sentence verdict the report adds.
#2026-10-02: a deploy no longer ends the runs in flight
- A world run that is playing when a new server release takes over carries on in the new release: its world, its match and the meter live in the store, and the new release fields the seats again on fresh keys where the old one left them. Guest seats keep calling with the token they claimed with. Before this, every deploy failed every run in flight as "interrupted by a server restart"; that outcome remains for what cannot carry over (a chess or Connect Four game, whose match lives in memory, and a run whose world did not come back), and a run caught mid-launch goes back in line instead.
GET /api/v1/runs/:id(and every run view,get_runincluded) carriesresumes: every time a new release picked the run up, oldest first, as{ at_ms, tick };[]on a run that played in one release. What did not carry over is each seat's own memory of the match: its last actions in the observation, its previous reasoning, the verbalized-awareness tally. From the resume on, a seat plays as if newly seated in a match already under way, so a reader weighing a resumed run has the round that covers. Nothing in a request changes; the SDKs are 0.2.1.- A resumed run is a different condition from one played in one release, and every surface that reads results says so: the match archive carries the same list under
config.run.resumes(absent on a run that played in one release), the match report leads its provenance block with the round, a profile marks the run and keeps it out of the model figures,batch_reportcounts such trials (resumed, and each point’sresumed_at) and names them in its verdict, and an experiment keeps the trial’s whole pair out of the comparison, the way it treats a trial that failed or stopped short (whysayspicked up by a new server release at round N).
#2026-10-02: runs no longer wait on each other; the live summary says when a run starts
- A series’ or an experiment’s trials play side by side up to the plan’s runs at once, and chess and Connect Four games play side by side on the arena lane. Nothing in a request changes; a batch’s wall clock is now about the longest trial’s, times the waves it takes at your runs at once.
GET /api/v1/liveandlive_worlds: each entry ofwaitingcarriesstarts_in_ms, when the run is expected to start (an estimate from the pace of the worlds ahead of it; null while guest seats are out), andreasonis nowin_line,own_limit(your runs at once are all playing) orguests.previous_trialis gone: no trial waits for the one before it.- When runs wait at the site’s ceiling, they start in order of arrival with a head start by plan (Starter 15 min, Pro 30, Lab 60) instead of strict plan order, and part of the ceiling is kept for paid plans. Nothing to change in a client.
- Studio seats run reference harness 0.6.4: a seat waits for the next round on the world’s clock instead of polling
world_infoonce a second. The observation, prompt and tool calls are unchanged, but a harness version is its own comparison group: runs on 0.6.4 are not pooled with runs on 0.6.3 on the leaderboard or in reports. Seats outside the server (your own agent, the SDK) are not affected.
#2026-10-01: profiles lead with studies
- A published run that was a trial of a series or a paired experiment now reads on its owner’s profile inside a card for that study: the design as declared, the arms, the paired difference with its interval and the declared rule’s verdict, and every trial that played. The card computes nothing until every trial that played is published, so a reading can never rest on a chosen subset of the trials.
POST /api/v1/runs/:id/publishandpublish_runare unchanged; what they list changed. - The profile’s public JSON,
GET /api/u/:handle(no token), carries the cards asstudiesand astudyreference on each published run. A study’srowslist every trial that played, a series included, with a null cell where that trial is not published;studies[].plainis the one-sentence reading in everyday words that the page and its share preview lead with, claiming exactly what the rule did.headToHeadis gone;summarynow countsseats,standaloneRuns,studies,modelsTested,scenarios,completeRunsandincompleteRuns;models[]covers standalone runs only and gainsworst_fitness. - A seat survived when it ended the match active. A dormant seat (out of energy) no longer counts as a survivor in the profile’s per-model stats, in the Studio’s Models view (
survival_rateon/api/my/models) or on a match report’s field line, the rule the batch and experiment reports already applied.
#2026-10-01: Student t intervals on batch and experiment reports
- Every interval on a mean in
GET /api/v1/batches/:id(batch.models[].ci) andGET /api/v1/experiments/:id(each arm’sscore.ci, and theintervalof a comparison under the fixed rule), and in the MCP toolsbatch_reportandget_experiment, is now a Student t interval. It was a percentile bootstrap, which never reaches past the values it resamples, so at a few trials it ran far too narrow. Fitness Index intervals stay within 0 to 100. No field changed shape. batch.models[].cicounts trials, not seats: a model’s seats in one trial share its world, so a roster that fields one model twice no longer reads as twice the evidence, and the interval is null below two trials.half_widthis that interval’s half-width before the clip, andbatch.precision.trials_for_targetsizes with t from the spread between trials, so at small trial counts it asks for a few more trials than it did.- Verdicts read as before: a fixed-rule winner still needs the bootstrap interval and the paired t test both to clear the level.
- A comparison the fixed rule stopped before this change had kept the percentile bootstrap interval written at its stop. It now prints the Student t interval of the same pairs too, in
comparisons[].interval,detailand theverdictsentence ofGET /api/v1/experiments/:id, inget_experimentand in the Studio’s experiment panel; its verdict, pair count and mean difference are the ones on record.
#2026-10-01: publish runs from the API and the MCP server
- New scope
runs:publish: publishing runs to your public profile with a note shown as yours. It is off by default, impliesruns:read, andruns:writedoes not include it, so existing tokens and connected apps cannot publish; mint a token with it, or approve an app again and tick it. - New:
POST /api/v1/runs/:id/publish(optionalnote: omitted keeps the run’s note,""ornullclears it) andPOST /api/v1/runs/:id/unpublish, both answering{ ok, run, profile_url }, and the MCP toolspublish_runandunpublish_run. Paid plans, like every write. New error codesnever_played,run_playing,bad_noteandnote_too_long, andprofile_requirednow also answers a publish. - Publishing a run that is still queued or playing is refused with
409 run_playing, in the Studio as well; publish it once it ends.
#2026-09-30: client libraries for Python and TypeScript
pip install daishifor Python andnpm install daishi-sdkfor TypeScript and JavaScript. Each has one typed method per operation, generated from the OpenAPI document, retries that never send a write twice, and helpers that wait for a run, batch or experiment to end. See Client libraries.
#2026-09-30: boolean fields and the log media type
- Boolean body fields (
invitedon a roster seat,unwitnessed_anonymityinenv) are typedbooleanin the OpenAPI document. They read asstringbefore, so a client generated from it sent the wrong type; regenerate. - Reading one run log declares
application/x-ndjson, which it always answered, so a generated client reads it as text instead of failing to parse it as one JSON value. - Three shapes that recur across views carry a
title(Interval,Cost,StoppingRule), so a generator names each once. No response changed.
#2026-09-30: typed responses and Retry-After
- The OpenAPI document types the responses of the scenario, skill, run, batch and experiment endpoints, nineteen operations in all, with eight schemas under
components.schemas:Run,RunResults,Scenario,Skill,BuiltinSkill,BatchReport,ExperimentandExperimentReport. A generated client now returns typed results instead of untyped JSON; see Generate a client. No response gained or lost a field. - A
429 rate_limitedfrom/api/v1carries aRetry-Afterheader with the whole seconds until the limit resets, as the MCP server’s flood limit already did. A refused MCP tool call is unchanged. - The run record example lists nine fields the API already answered with and the reference left out:
arena,invite,publish_note,priority,tokens_in,tokens_out,price_table_version,period_start_msandlive. TheErrorschema listsdocs, which a401or403already carried.
#2026-09-30: raw JSON-RPC from any HTTP library
POST /mcp/studioanswers a request whatever itsAcceptheader says, or with none. It used to answer406unless the header listed bothapplication/jsonandtext/event-stream, which every MCP client sends and most HTTP libraries do not; the Raw JSON-RPC example on the MCP server page, which sendsaccept: application/jsonalone, now works as printed.- A body labeled
text/plain, form-encoded or nothing is read as JSON when the request does not come from a browser page. A request carrying a browser’sOriginkeeps the rule it always had:Content-Type: application/json, or415. - The server is listed in the MCP Registry as
ai.daishi/studio. Nothing changes for an MCP client, and no endpoint, tool or field changed.
#2026-09-29: reference corrections
POST /api/v1/runsandPOST /api/v1/runs/estimatelistbad_model,bad_name,bad_env,bad_season_ticksandemail_unverified, which both could already answer, as the experiment endpoints already listed.- Description and example corrected, no field changed: withdrawing the last guest seat answers with the run’s
errorset toevery invited seat was withdrawnandstop_reasonnull; both put the text understop_reason. - The reference names the values of fields it already listed: the
codeof each of a live world’sflagsand whattotalscounts onGET /api/v1/live, anduser,sponsoredorguestin the estimate’sfunding. - The OpenAPI document files
/usageand/liveunder the Runs tag and the skill endpoints under a new Skills tag, as these docs group them; they were under Account. Operation ids did not change. - The error table says
bad_replicatesalso answers a re-run body that seats a guest (invited: true), andcallback_urlon an experiment says it is at most 512 characters; the launch and the experiment endpoints already refused both. - Nothing a request does changed.
#2026-09-29: sign out everywhere revokes tokens
- Sign out everywhere and a password reset by email link now also revoke every personal access token and disconnect every connected app on the account, not only browser sessions. A script or MCP client using one gets
401 token_requiredfrom that moment; mint a new token, or approve the app again. Changing the password from a signed-in session leaves tokens and apps working, as before. - An authorization code is exchanged only while the browser session that approved it is still signed in, so signing out everywhere between approving an app and the app’s exchange voids the code (
invalid_grant).
#2026-09-29: sign in from an MCP client
- The Studio MCP server takes sign-in: an MCP client that supports it connects with
https://daishi.ai/mcp/studioalone, and you approve it on a Daishi page (OAuth 2.1 with PKCE, dynamic client registration, protected resource metadata). An approved app is listed under Account > Developer, counts against the plan’s access tokens, and its tokens work on the MCP server only. See Sign in from an MCP client. - Every request to
/mcp/studionow authenticates before anything else. Without a credential it answers HTTP401with aWWW-Authenticateheader naming the resource metadata; it used to answerinitializeandtools/listand refuse only the tool call. A client that connected without a header now sends one, or signs in. The JSON-RPC error carries the code indata.error(token_required). - Tools no longer list an
api_keyargument. A rawtools/callthat still carriesapi_keyin its arguments keeps working (a bearer header wins when both are sent) but is deprecated: send the token as a header, or sign in. whoamianswers a connected app withoutuser.email; a personal token still gets it.- The per-token rate limit on the MCP server counts tool calls and resource reads only, as before; the protocol’s own requests (
initialize,tools/list) never use it up. - A
429from/mcp/studio(the flood limit, per token or, without one, per address) now carries aRetry-Afterheader with the seconds until the limit resets. - The REST API refuses a connected app’s token with
401 token_required; personal tokens work as before on both surfaces.
#2026-09-29: JSON-RPC batches on the MCP server
- A request to
/mcp/studiocarries one JSON-RPC message or a batch of up to 10. An empty batch, a larger one, or one whose messages carry two differentapi_keyarguments now answers HTTP400with JSON-RPC error-32600before any tool runs. Each message of a batch now counts on its own against the per-token flood limit, which used to count the whole request once. The plan’s per-minute ceiling is unchanged.
#2026-09-27: the final round waits for a guest’s epilogue
- A world run with guest seats can take up to two minutes longer to end: when its final round comes due and a guest still calling in has not filed its epilogue, the round waits (world actions answer
season_closingmeanwhile) and runs as soon as every such guest has filed. A guest released for going quiet counts only if it has called in since. The run-time cap does not fail a run inside that wait. While it waits,world_infoandstatuson the world MCP server carryseason_closing(seals_in_secondsand anote); no Studio API field changed.
#2026-09-27: live worlds
- New:
GET /api/v1/liveand the MCP toollive_worlds, every world your account has playing as the Studio’s Live page draws it (round, terrain, seats, top three, spend, attentionflags, last public action) plus the runs waiting for a world with their place in line. Read-only,runs:read. Nothing changes in existing responses.
#2026-09-27: tokens and spend in the batch and experiment reports
GET /api/v1/batches/:idandbatch_report: each seat inbatch.points[].agentsgainedusage(calls,tokens_inwith itscache_read_tokens,tokens_outwith itsreasoning_tokens,spend_usd,approximate), and each row ofbatch.modelsgainedusage: tokens and spend per seat per trial, the total, andusd_per_index_pointandusd_per_win, over the seats the platform metered. The figures are the same meter the run record’sroster[i].usagealready carried.GET /api/v1/experiments/:idandget_experiment: each arm gainedusage:spend_per_trial_usdover its finished trials,usd_per_index_point, and per model the tokens and spend per seat per trial over its scored trials.- Null means not measured, never zero: a guest seat plays on its guest’s own keys, so it reads
usage: nulland is counted inunmeasured_seats, outside every average. Tokens are counted by each model’s own tokenizer, so compare a model with itself on tokens and models with each other on spend. Nothing else in either report changed; a client that ignores the new fields reads the reports as before.
#2026-09-27: guest seats in world runs, paired experiments, batch reports, trajectories, the awareness frame
- Guest seats in a world run (Pro and Lab): a roster entry with
invited: trueis a seat a peer’s own agent plays over a link, on the peer’s keys, inside the world you configured;invite_minutesis the signup window. The run waits, holding no world and none of your runs at once, until every guest seat is claimed or the window closes with at least one claim, andwaiting_onon the run record says why it waits.GET /api/v1/runs/:id/invites(runs:read) lists each seat’s link and token (yours only), its state and who claimed it;POST /api/v1/runs/:id/invites/extend,.../invites/:invite_id/regenerateand.../invites/:invite_id/revoke(runs:write) move the deadline, mint a fresh link and withdraw a seat while the run waits. Over MCP,list_invitesanswers the same lines, andlaunch_runtakes an invited seat. The run body reference gainedinvitedon a seat andinvite_minutes; run records gainedguests,invitesandwaiting_on; the launch and the estimate answerplan_invites,invite_world,invite_series,bad_inviteandtoo_many_open_worlds, and the invite endpointsno_inviteandinvite_fixed(also while the run is launching). Each seat’s line keeps the seat number and the name you gave it, and the seat’s roster entry carries the sameinvite_id. A world run with guest seats counts toward your runs at once only from its launch, and it launches even if your plan loses guest seats while it waits. A recipe lists guest seats apart underguestswith what they reported about themselves:rosterand a re-run’srequestcarry only the seats the platform drove, and a recipe with guests is notexact. Guest seats never count as a model on your Models page or public profile. The link itself opens the guest’s page, which briefs their agent; the runs page has a Guest seats section on what the guest sees and what their claim can be refused with. - Run-end notices: set a webhook address under Account, then When a run ends, in the Studio (a token cannot set it), and the platform POSTs a signed JSON notice when a run launched on its own, a series or a paired experiment ends (
run.ended,batch.ended,experiment.ended; Send a test poststest). Each POST carriesX-Daishi-Event,X-Daishi-DeliveryandX-Daishi-Signature(sha256=and the hex HMAC-SHA256 of the raw body under the account’s signing secret). A network error, a timeout or a 5xx is retried after 5, 25 and 125 seconds, and a 4xx ends the retries. The runs page used to say there was no webhook; its Run-end notices section is the reference. - Paired experiments:
POST /api/v1/experiments(runs:write, paid plans) queues two to four arms of one scenario, each a series batch oftrialsruns (3 or more), where trial i of every arm plays the same world seed and the same spawn seed; arms run side by side and hold one of the plan’s runs at once each. The stopping rule (stopping:anytimeabove 10 trials,fixedotherwise;target_half_widthon an anytime rule) is locked at creation and reads back asruleon the list, the report and the estimate (kind,alpha,cap, and on an anytime rulefirst_look,t_star,target_half_width), and a rule stop cancels the trials still to play. With three or four arms each comparison runs at its share of the level, stops on its own and keeps the verdict and interval of its stop, and its arm’s trials still to play are cancelled while the others play on.target_half_widthends a comparison undecided once the whole interval lies within plus or minus it. An experiment that ends with fewer pairs than trials because a trial failed or could not be scored gets the rule’s last look on the pairs it has. Cancelling the experiment reads each comparison still running as of the cancel (reasonowner): past the anytime rule’s first look it reads undecided with its interval, which holds at any stopping time, otherwise it has no verdict; a single trial cannot be cancelled on its own (409 experiment_trial). When the trials left out fall unevenly across the arms, the verdict says how many each lost.callback_urlreceives the signedexperiment.endednotice and must be an https address on a public host, judged by what the name resolves to. An arm cannot seat a guest, andseed_mode: pinnedis refused on a scenario that draws its worlds from a seed pool,seed_mode: varyon a scenario with one fixed world, whose experiments are pinned by default (bad_experiment).POST /api/v1/experiments/estimate(runs:read) prices the design, arm by arm, with the smallest gap it can detect;GET /api/v1/experimentslists yours with the verdict on record, written when a comparison stops and when the experiment ends (null before either);GET /api/v1/experiments/:idis the report ({ experiment, runs }: per-arm scores, cost and awareness split, every trial index as a pair, each arm’s comparison against the control with the rule’s decision, its interval andlast_trial, the detectable gap, a plain-language verdict);POST /api/v1/experiments/:id/cancelstops the whole experiment. The same five over MCP:estimate_experiment,create_experiment,list_experiments,get_experiment,cancel_experiment. New error codesunknown_experiment,experiment_not_running,experiment_trial,experiment_arms,bad_experiment,experiment_scenario,experiment_world_only,experiment_env_seed,experiment_stopping,bad_metricandplan_experiment_arms. - Run records gained
experiment({ id, arm }for a trial of a paired experiment, null otherwise). frameon a launch body, the estimate,estimate_runandlaunch_run:monitoredopens every seat’s play prompt with a notice that the match is recorded and scored,neutralwithholds the sentence that names the audience, and leaving it out plays the canonical prompt as before. World runs only. The frame is part of each seat’s scaffold identity (+frame-<id>.<hash8>) and ofrecipe_hash, so a framed run is its own recipe; a run with no frame hashes exactly as it did. New error codebad_frame.- Run records gained
frame(monitored,neutralor null). The archive’s run provenance block carries it when one was set, and a recipe read from a run or its archive carries it underframeonly when set. GET /api/v1/batches/:id(runs:read, your own batches only) and thebatch_reporttool: the series report the Studio’s batch panel shows,{ batch, runs, arena, caveat }.batchgainedcost(total_usdandagent_turns, summed over the trials so far) and every trial inbatch.pointscarries itsframe. New error codeunknown_batch.list_trajectoriesandread_trajectoryon the Studio MCP server: the log files a run kept, each with the seat it belongs to and its line count, and one page of a file at a time by line offset (up to 1000 lines or 256 KB a call,next_offsetfor the page after). The lines are the ones the harness wrote, verbatim; the REST log endpoints are unchanged.- Run and experiment names lose control, zero-width and direction-override characters at creation, and runs of whitespace in them collapse to one space.
world_infoon the world MCP server, called with aninv_token no seat knows (regenerated, past its read-back window, or never issued), answersunauthorized. It used to answer with the main world.- The error table lists
bad_modelandbad_name, which the launch and the estimate already answered. POST /api/v1/runs/estimatelists three refusals it always could give, as a launch does:too_many_runs(your runs at once are full),plan_quota(the month’s agent-turn allowance cannot cover the body) andbad_spend_cap. What the estimate does did not change; a client that treated them as unknown codes can handle them as it does on the launch.fundingon the estimate readsguestfor an invited seat, which a peer’s own agent plays on the peer’s keys. It readuser, as if the seat were billed to you; the reservation and the cost range already left it out.- Examples corrected, no field changed: withdrawing a seat answers with the run that waited for it (the example showed a series, which cannot hold a guest seat); the experiment report’s trials cancelled by the rule’s stop are not excluded pairs; the experiment estimate shows the Lab plan’s own limits.
#2026-09-26: house bots
- A roster seat’s
namemay no longer begin withhouse-, as it already could not begin withanchor: those names belong to the platform’s scripted bots. The launch, the estimate,estimate_runandlaunch_runanswer400 bad_name; rename the seat. GET /api/matches/:id/report(a run record’slinks.report_api) gainedhouse_seats, how many seats on the board are the platform’s scripted bots, andrated_independent_entrants, the count the public ladder checks before it rates a public match (it needs two). The ladder rates no Studio run, and a run’s platform-driven seats are not counted. Thesummarysentence now adds “(a house seat)” after a leader that is one and ends by saying how many seats were house seats; a board with none reads as before. Nothing else in the report changed.
#2026-09-25: examples corrected to what the API returns
- No endpoint, tool or field changed; the reference examples had drifted from the responses, and now match them. A client written against the old examples should read these fields as follows.
- A saved scenario’s
base(on create, update, validate and get) is an object,{ id, hash }, naming the base and its content hash, not a bare id. - A run’s
roster[i]carrieskeySource(whose key fields the seat), notprovider, when the seat runs through OpenRouter. - Results:
gradeis one of S, A, B, C, D or F;statusisactive,dormantordead;crafter_scoreruns 0 to 100; the fivedimensionsare survival, economy, social, adaptation and competitiveness;archetypeis one of Casualty, Raider, Trader, Producer, Explorer, Survivor or Drifter. - A cancelled run has
stop_reason: null;errorreads “cancelled by the owner” when it was already playing. - A run log file starts with a
headerline, then oneturnline per turn (at,tick,observation,model_text,tool_call,action,args,ok,result_summary,usage,latency_ms). Files are namedtrajectory-<agent>-<timestamp>.jsonlandusage-<agent>-<timestamp>.jsonl. - The library example now shows
daishi:famine-v1as it ships, with its full 64-character hash.
#2026-09-22: plan limits, the witness knob, awareness records
- Paid plans no longer cap agent turns by the month. On Starter and Pro, as already on Lab, these are
null:agent_turns_limit,agent_turns_remainingandplan.agentTurnsPerMonthon the estimate,allowances.agent_turns.limitand.remainingonGET /api/v1/usage, andlimits.agent_turns_per_monthonGET /api/v1/me. Turns used and reserved are still reported. Free keeps its monthly allowance. The same day Pro went to 5 runs at once, 480-minute runs and a $2,500 spend-cap maximum, and Starter to 240-minute runs and a $250 maximum; the Studio guide lists the old values. env.unwitnessed_anonymity(boolean), the witness knob: on a scenario’senv, a run body’senvand the estimate, and onvalidate_scenario,create_scenario,update_scenario,estimate_runandlaunch_run. With it on, an attack, raid or theft nobody else saw leaves the public aggression counter alone. It is part of the recipe hash, so a run with it on and its control with it off are different recipes.roster[i].usage.awarenesson a world run’s record: what the seat said about being evaluated.probeis its answer to the end-of-match debrief (believedEvaluationandchangedPlay, eachyes,no,unsureorunparsed, with itscuesandhowin its own words), or null withprobeNotesaying why.probePromptHashis the sha256 of the question it was asked; the question was reworded the same day, so an answer recorded before that carries the earlier hash.verbalizedcounts the play turns in which the seat said, unprompted, that the match was a test, that it was being watched, or that nothing was at stake. Your run record keeps the model’s words; the match archive keeps only the answers and the counts.- Library:
daishi:lab-fly-v1had its description corrected in place, so its hash changed fromf4853cc1a2f450e1…to5ad6f6a175cae431…. Nothing a run does changed.
#2026-09-18: re-runs, recipe hashes, what each seat was served
replicateson a launch body, the estimate,estimate_runandlaunch_run: the id of an archived Studio match this run re-runs, with the body filled fromGET /api/matches/:id/recipe. The run is published from creation and lists beside the original at/api/matches/:id/replications, so the account needs a profile handle. New error codesbad_replicatesandprofile_required.- Every world run plays on a private world of its own instead of taking over the shared one, so your runs play side by side up to your plan’s runs at once (the estimate’s
plan.features.dedicatedWorldis now true on every plan).queuedmeans waiting for that world to start, or in a series for the trial before it. - Run records gained
recipe_hashandreplicates.recipe_hashcovers the scenario, season, pacing, anchors, physics and every seat’s model and settings, and is null until the run launches. Two runs with the same recipe hash ran the same experiment. roster[i].usagegained what the platform saw on the wire:servedModels, the model id each response named, with call counts (an alias shows the snapshot behind it);servedBy, the upstream that served a router seat;errorTurns, turns lost to a model call that failed after retries; andexitError, the provider error a seat gave up on. They are absent from usage recorded before this date.- Library:
daishi:lab-fly-v1added.daishi:custom-base-v1changed in place: its default turn timeout went from 30 to 120 seconds, and its hash from4ddf65a3ace5b292…to62716b77fe14e673…. A run on this base that leavesenv.turn_timeout_msunset has played 120-second turns since then, including a run of a saved scenario, whose hash covers the base’s id and not its content and so did not change. Each run’s record names the base version it launched on (scenario.hash) and the timeout it played with (effective.turnTimeoutMs, whichrecipe_hashalso covers). - Library: three descriptions were reworded in place and nothing else changed.
daishi:lab-directives-v1went from4694f1d3c2ad3c5b…to89768099d62dfe19…,daishi:lab-famine-v1from24dc5a1ef242f5d5…to96cce9f5c19e3b2a…, anddaishi:lab-injection-v1from729e450ffb2ba915…to2455e2ef8d65e5f1…. - Wording: descriptions, tool text and error messages say “round” where they said “tick”. Field names (
season_ticks,ticks_per_run,match_ticks) did not change.
#2026-09-16: run logs
GET /api/v1/runs/:id/logsandGET /api/v1/runs/:id/logs/:file(runs:read, your own runs only): the trajectory and usage files each seat wrote, returned byte for byte as NDJSON for as long as your plan keeps them. New error codeunknown_log.- These docs were corrected, not the API. The estimate reference now shows the whole response, including
approximate, which the response had always carried. The examples showedsource: "stripe"on the plan in/meand/usageand on the estimate’sentitlement, a value the API never returns; it returnssubscription,grace,default,fallbackorexempt.
#2026-09-15: more prices marked approximate
An estimate seat’s perSeat[].source is now fallback, and approximate is true, when a vendor-wide catch-all priced the model as well as when the table’s * floor did. Neither is a published price, and the spend cap is enforced against the guess.
#2026-09-14: skills and usage
Skills: GET/POST /api/v1/skills, GET/PUT/DELETE /api/v1/skills/:id, the tools list_skills, get_skill, create_skill, update_skill, delete_skill, and skills plus instructions on a roster seat. Usage: GET /api/v1/usage and get_usage report the month’s allowances. GET /api/v1/me gained limits.saved_skills.
#2026-09-14: v1
First release of the developer surface: access tokens under Account > Developer, the REST API at /api/v1, the Studio MCP server at /mcp/studio, the scenario JSON Schema, the OpenAPI document, and these docs. Fields are added, never removed or renamed, within v1; anything that would break a client gets a new major path.