Repository navigation
feat(catalogue): add OSWorld2 with OSWorld-V2's own agents, its services on the machine and a null control - #134
Draft
sirotaoleg wants to merge 31 commits into
Draft
sirotaoleg wants to merge 31 commits into
sirotaoleg wants to merge 31 commits into
Conversation
On OpenRouter one model is served by several providers, and a run could
neither say which may answer nor see which one did. `--served-by
[MODEL=]SLUG[,SLUG...]`, repeatable, names them for a model (the run's
when MODEL= is left out). Every relay of the run, a container's sidecar
included, puts `provider: {order: [...], allow_fallbacks: false}` on each
request it sends as that model, on any route and in the three API shapes,
in place of the caller's own routing field. A pinned Chat Completions
request asks for OpenRouter's routing facts, where its reply names the
provider that served it. Without the option a request goes on byte for
byte as before.
The option is refused before any unit when the run's provider routes no
model to several (only openrouter does), when it names a model the run
does not call, or when a slug is malformed or given twice.
Each call's record keeps the routing sent (and the caller's own when it
was replaced), the serving provider the reply names, how the reply ended
and an error the reply holds under a 200, which now makes the call a
failed one. The job's config records the pin by model; each trial and
the job say the answered calls by serving provider and, for a pinned
model, whether every one was served by a pinned provider. merge refuses
shards whose pins differ, unless --force.
The run page gains the option and its rules: the slugs, what the relay sends, the refusals, the record and how a pin reads as held. The standard page names the record's new fields and the pin among merge's checks, and the providers page says that a run may choose OpenRouter's serving providers.
…ge no outcome without a pin A review found the pin's record could copy credentials, miss calls and change what a run without the option records and decides. Now: - The facts of a reply (who served it, how it ended, an error under a 200) are read by reply_facts, apart from read_reply, which is as before: the text, tool calls and usage a record held are read as they were, and no fact can raise into the delivery of a reply. Every choice is read for its end and its error, by index; a top-level error is read whatever else the reply holds; a completed Responses event's routing facts name who served it; a fact of the wrong type is left out. - An error under a 200 is recorded beside the reply and nothing else follows from it: the call keeps its status and failure reads it as before. - The recorded routing objects and facts are cleaned: a value under a credential's name, the relay's own key and key-shaped strings are removed. What is sent is unchanged. - The routing-facts header goes to OpenRouter alone, as enabled whatever the caller sent. - One check of a pin, shared by the run, its command line and the relay's own command; a control is refused a pin on a model it never calls. - Who served a unit counts its judge's and simulated user's routes, and the job the benchmark's own relay's calls; the tokens and the cost stay the agent's. A trial whose answered calls name no provider keeps its count. A name reads as a slug by its case and spaces alone. - merge compares pins as mappings, and a forced merge of different pins reads held unknown for such a model.
…held cannot tell The pages now say that an error under a 200 is recorded and changes nothing else, that the record's copies are cleaned of credentials, that who served a run counts every route, how a name reads as a slug, and that held names a provider, never its endpoint variant.
… it must refuse a pin
A run started from the builder could not pin its serving providers. On OpenRouter, the Model step now shows Served by under each model field (the model, the user simulator's and the judge's): the providers that may answer that model, tried in their order and no other, typed as slugs or added from the model's providers. Empty, the default, pins nothing. The recipe holds them as served_by, by field. The server checks them as --served-by is checked, refuses them for another provider or for a model no job calls, and compiles them into --served-by for each job that calls that model, by the name the run sends it as. Review shows them in its Model row. A saved recipe keeps them, and choosing another provider drops them. A recipe with no pin compiles to the command it did. The model's providers come from a new route, POST /new-run/endpoints, which asks OpenRouter's endpoint list as /new-run/models asks the model list: local and form-token guarded, the key kept on this machine and sent when set, the answer cached by the page for five minutes, and a fixed message when the list cannot be had, the field then taking a typed slug.
… model's providers
…del it was set for A review of the pin found records and summaries that could read held where another provider answered, credentials that could reach a record, and pins that the builder dropped or moved to another model. Now: - The provider recorded is the one of the reply's end: a stream's latest event that names one, and the last attempt of OpenRouter's routing facts; an earlier, failed attempt is never taken, and a last attempt that names none gives none. - An error keeps its code, type and message alone, of those kinds; the rest of the object is not kept. A recorded routing object's keys are cleaned as its values are. - A merged job's summary is made of each shard's whole one, its benchmark relay's calls and an unknown included. - A recipe's pins are resolved by model, then given to every job that calls that model, a control's judge job included. The compile route returns each field's model as the run sends it, and Served by lists that model's providers. - A control is refused a pin on its simulated user's model, which it never calls. relay serve refuses a pin unless its base is OpenRouter's (or --routing is given) and the model is one it sends; a container's relay is given the pins of the models it sends. - The builder clears a field's pin when its model changes, a preset or a benchmark included; keeps a saved pin as written, for the server to refuse what is wrong in sight; keeps an empty part of a typed list; puts its help in a tooltip; and keeps the keyboard on the model field once the form is drawn again.
…d where held reads unknown A variant may be a quantisation, a speed or a region. Of several choices' errors the record keeps the top-level one, else the first choice's. A provider whose name is not its slug once lowercased reads unknown though it served the call; the example of a name that holds is DeepInfra.
…facts mark as selected OpenRouter's routing facts list the endpoints available for a request and mark the one that served it as selected; they hold no list of attempts. The relay now reads that entry, at the top of a reply and in a completed Responses event's response, and asks for the facts on a pinned Responses request too: a Responses reply names its provider there alone, so a pin on a model an agent calls through Responses could never read as held.
… pin no provider serves gets 404, and why held may read unknown
…ake no name a chunk gave before
… asks for the routing facts and where held reads unknown
main gained the OSWorld1 benchmark and the run option --units since this branch started. Seven files met: the command line, the job's record, merge, the run and three pages. Both sides are kept whole, main's first: a run takes --units and --served-by together, config.json records standard.units_file and standard.served_by, and merge compares both (different unit lists are always refused, different pins unless forced).
…s record A run given --units and --served-by from the command line records both in config.json, with and without --from-run.
…n the field guide The field guide names both config fields, the routing fields of a call, the serving counters and held, and says that merge refuses different unit lists always and different pins unless forced.
main gained the settings that say what Nullius may use on each machine, with the run option --constraint, and a fix of the settings page. Seven files met: the command line, the job's record, the recipe, the run, a test module and two pages. Both sides are kept whole, main's first: a run takes --constraint and --served-by together, config.json records standard.constraints and standard.served_by, and a recipe compiles each target's constraints and the pins of the models its jobs call.
Seven cases where the two meet: both records from the command line, with and without --from-run; the order of the refusals; a scope's enforcement with a valid pin; a container relay's caps beside its pins; each target's compiled command; the builder's checks; and shards whose machine limits differ.
…ving pin in the field guide The field guide says that standard.constraints, standard.units_file and standard.served_by may stand together in config.json, and which of them a merged job keeps from its first shard.
…on top of --served-by: gated tasks, a definition's pins as its serving pins, a pinned route's refusal as the unit's, limits of their own, and prove of a merged control A definition's pin (setups.original.pin, score.judge.pin) joins the run's --served-by pins and wins over --served-by for its model; ApiProviderRouteError reads the pin and reply.served_by. A failed Responses object reads reply.end and reply.error.
…eep the score of a unit a guard stopped despite a last 429 limits.run_again false: a unit that ended rate-limited is not run once more; its outcome stands. A unit whose driver scored it after a guard's stop keeps that score, marked stopped, though its last own call got a 429.
… own last 429, not a side call's or a context window's stop The rule reads the agent's own last call, a guard's line in the stop file and a score stopped by other than the context window; a cap's refusal is still the cap's. limits.run_again takes null as unset. The rate limits row names the opt-out.
… upstream's own runners, mock websites, GitLab and proxy, proven on test_v2, and how to prepare a machine for it OSWorld2 runs each of the 108 tasks of test_v2 in a VM of its own through the upstream's docker provider, with the mock websites its tasks need served on the machine, and GitLab and a proxy started for each unit that needs one. Its Original runs the upstream's agents with their models pinned to their providers on OpenRouter, and its null control is proven on test_v2 at 0 of 108. The tasks are gated, so a failing endpoint is kept as its status alone. Its machine page says what a machine needs, how to check it and what a run costs.
…reach OSWorld2's VMs and services
limits.run_again false: an OSWorld2 episode is paid and long; one that ended rate-limited is run again by hand.
…g programs, and fake the sweep's clock for the sweep alone Two tests made a Run of OSWorld2, which asks for ImageMagick, ffmpeg and tesseract on the machine: they now say the programs are there, as the test of their refusal does. The sweep's tests replaced time.sleep for every module, so a slow docker's wait in subprocess advanced the fake clock and the test's own steps: the fake now replaces the sweep's time module alone.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #133, itself on #124: this pull request's own commits are the last five. Until they are merged, the diff shows their files too. It can be merged after both.
What
OSWorld-V2's 108 tasks become a bundled benchmark,
osworld2, on the Nullius standard, at the upstream's releaseosworld-v2.1. Each task is a unit in an Ubuntu VM of its own on the upstream's docker provider, set up by the task's own class and scored from 0 to 1 by its own evaluate(). The tasks and their assets are gated on Hugging Face. Nullius does not fetch them, and the entry isgated(#133): a unit's failure from a model endpoint says its status alone, and the driver names an upstream exception by a known class's name at most, never by its words. The Original setup runs one of OSWorld-V2's own agents, M3 or Muse Spark, chosen by the Agent option, from its published script, with every model call sent to the relay. The Original's models are pinned to their providers on OpenRouter: M3 to MiniMax, Muse Spark to Meta. Claude Sonnet 4.6 is pinned to Anthropic; it is the LLM grader of 21 tasks and the simulated user of 6. Only M3's runs count as comparable with the paper's row. Its Review row and its generated page name the setup "OSWorld-V2 agents". A null control runs oneDONEaction, then the task's evaluate() on the untouched VM. The entry declaresagent.computer/v1. No agent from the Agents page runs it yet. Its texts are one Good to know line and 25 limitations. Settings lists GitLab's root token under Custom keys, for the two GitLab tasks.The mock websites of 40 tasks are OSWorld-web at the same tag. The guide's steps build them from source and serve them on the machine the units run on. A unit that needs them is refused before its VM starts when they do not answer. For 026 and 041, the driver starts GitLab anew from its snapshot, one task at a time, and Overleaf's 2 tasks get Overleaf started anew. Each of the 8 tasks that ask for a proxy gets a proxy of its own. The options are Steps (100, 300 or 500), Thinking for M3, the website host suffix, the GitLab host and its write share, and disk write limits for each VM. By default a run takes 100 steps with thinking off, as the upstream's M3 script does; the paper ran M3 at 500 steps with thinking on. Muse Spark runs as published at this release, while its published score came from newer upstream code; a limitation says what differs. There are six datasets: All tasks (108, the upstream's
test_v2.json), Offline (82), and four by the services their tasks need. Tasks 103 and 104 are not supported: their evaluate() needs FreeCAD on the driver's machine. Review and the run's start ask for Docker and for the scoring programs (ImageMagick, ffmpeg, tesseract) on the driver's machine. As for OSWorld1, the Where step greys out the container card under the Original, whose docker provider needs Docker on the machine. A unit that ends rate-limited is not run again by itself (limits.run_again: false): an episode is long and paid. A guide,docs/benchmarks/osworld2-machine.md, prepares a machine, runs the null control on one task as its last setup step, and says what a run takes in time and money.Touches
catalogue · templates and static (the mark) · docs and site (
docs/benchmarks/, the generated pages,AGENTS.md) · routes (tests only) · packaging (the entry's.yml,.Dockerfileandstartup/*.pyas package data inpyproject.toml)Before and after
The Benchmarks page before this change
OSWorld2 listed, with its mark
The builder's Benchmark step under Computer use: OSWorld1, beside the selected GAIA
OSWorld1 and OSWorld2
The Agents page: "n/7 benchmarks it runs"
"n/8": OSWorld2 counts, and no agent runs it
Settings, Custom keys: "No custom keys yet."
OSWorld2's
GITLAB_PRIVATE_TOKEN, not setTaken at 1400 px, light theme, on a fresh profile; the last three pairs are the top 1000 px of their page.
Verified
57851a44, on Python 3.12: backend suite (python3 app/tests/parallel.py), 2489 tests in 136 modules, OK; route suite with Node on PATH, 433 tests in 41 modules, OK.test_standard_gated.py, its tests in the classDefinitionoftest_standard_relay_pin.py)./ui-sweep --only osworld2atfc0bdb1f, this pull request's third commit: 6 screenshots, none with a page error or cut text.PYTHONPATH=app python3 -m nullius standard check: ok.python3 app/tests/catalogue_docs.py --write, then the check: up to date.python3 app/tests/field_contract.py: ok.app/site/readme_media.py --check: current. The docs check reports the same two findings inAGENTS.mdas on feat(standard): choose the serving providers that may answer a model, in the builder and on the command line #124's branch.c3637ffdis this pull request's tree with main's two later commits merged in;f74d1a00and58c2b586add one test commit: two JSON files, no code. It gives OSWorld2's Original one more model, MiniMax M3.1 Flash, the name it is served as, its pin to MiniMax and--max_tokens 32768. That model is a test stand, not in this pull request;58c2b586is this entry on the core before feat(standard): choose the serving providers that may answer a model, in the builder and on the command line #124;34e35370and8e2e381bare of the port before its move to this repository's history.standard.json'sproven:fc0bdb1f, this pull request's third commit; the fourth only writes this record, and the fifth changes three tests. The null control ran on all 108 tasks, one launch with each task a shard of its own, at 16 MB/s and 160 IOPS a VM, in six lanes with at most seven VMs at a time; it took 1 h 32 min. 108 and 093 failed their setup under that load and passed alone at 24 MB/s and 240 IOPS, in 24 more minutes. The grader cost $0.14.nullius standard prove osworld2 --dataset test_v2 --job nop=<merged job>passed: the 108 ids are the same twice, 0 of 108 at full score, a mean of 0.0021 against a bound of at most 0.01. Five tasks score above 0 on the untouched VM, as a limitation says;34e35370, which had the same mean; 077's LLM grader gave 0.054 both times.Seven kinds, on
58c2b586, by the test stand's model in OSWorld-V2's M3 agent, from the command line:Each of the seven gives its recorded score again at
c3637ffdwhen its outcome is computed from its saved records, with no VM and no evaluate().The eighth, the simulated user: 007, 0.8, by Muse Spark on
8e2e381b; the simulated user answered 7 times. It was not run on this base.c3637ffd, the null control on 087, a graded task: 0.0. The grader's call went to Anthropic by the definition's pin, andstandard.servedreadsheld: true;f74d1a00, the test stand's model on 052: 1.0. All 68 calls went to MiniMax by the test commit's pin and named it,held: true, with no error.90fde73e), and scored 0.0: the agent declared the task failed at its 120th step. Muse Spark ran none. Their scores before the move, on34e35370and earlier:Risk
agent.computer/v1. Settings shows OSWorld2's GitLab token under Custom keys to every user.osworld-v2.1, the grader through OpenRouter, and by default 100 steps with thinking off. A wrong error kind would move a unit between "environment error" (left out of the adjusted rate) and the agent's error (counted).-jbounds them.startup/sitecustomize.pywraps the creation of the VM's container: its network, its ports on 127.0.0.1, its disk and its write limits. The VMs need a free Docker subnet.