Spaces:
Running
Running
Lost Update v0: OpenEnv environment for concurrent writes to a shared JSON document
345483c verified Download server/tasks.py from jayzuccarelli/lost_update_env: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/spaces/jayzuccarelli/lost_update_env/resolve/main/server/tasks.py
- Command line
-
hf download hf://spaces/jayzuccarelli/lost_update_env/server/tasks.py
-
curl -L -o tasks.py https://huggingface.co/spaces/jayzuccarelli/lost_update_env/resolve/main/server/tasks.py
17.2 kB
| # SPDX-License-Identifier: BSD-3-Clause | |
| """Seeded episode generation: document, agent intent and rival schedule. | |
| `generate(seed, tier)` is a pure function. The same (seed, tier) always gives | |
| the same document, instruction and rival writes, which is what makes episodes | |
| reproducible and lets the oracle reconstruct the intent without reading any | |
| server state. | |
| Tiers: | |
| T0: no rival activity (control). | |
| T1: one rival write to a disjoint field, landing just before the agent's | |
| first write attempt. | |
| T2: 2 to 4 rival writes to disjoint fields, one before each of the agent's | |
| first k write attempts. | |
| T3: one rival write that overlaps the agent's intent in a compatible way | |
| (both bump the same counter, both append to the same list, the rival | |
| changes a value the agent's edit depends on, or the rival renames the | |
| key the agent must set). | |
| """ | |
| import random | |
| from dataclasses import dataclass | |
| from .ops import Path, apply_ops, describe, get_path | |
| TIERS = ("T0", "T1", "T2", "T3") | |
| PEOPLE = ["ana", "bo", "chen", "dara", "eli", "femi", "gus", "hana", "ivo", "jun"] | |
| ORIGINS = [ | |
| "https://app.example.com", | |
| "https://admin.example.com", | |
| "https://docs.example.com", | |
| "https://beta.example.com", | |
| "https://status.example.com", | |
| "https://partners.example.com", | |
| ] | |
| FACTS = [ | |
| "prefers short answers", | |
| "works in UTC+1", | |
| "allergic to peanuts", | |
| "has a dog named Miso", | |
| "is learning Rust", | |
| "runs on Tuesdays", | |
| "reads on paper", | |
| "uses a split keyboard", | |
| "drinks decaf after noon", | |
| "keeps notes in markdown", | |
| ] | |
| TOPICS = ["travel", "budget", "fitness", "recipes", "garden", "taxes", "music", "books"] | |
| TODO_TEXT = [ | |
| "renew passport", | |
| "book dentist", | |
| "fix the flaky test", | |
| "water the basil", | |
| "send the invoice", | |
| "update the README", | |
| "order printer ink", | |
| "back up the laptop", | |
| "call the landlord", | |
| "review the PR", | |
| ] | |
| LABELS = ["home", "work", "urgent", "errand", "later", "health", "money"] | |
| DEVICES = ["laptop", "phone", "tablet", "tv", "watch", "desktop", "speaker"] | |
| LIST_VOCAB = { | |
| "owners": PEOPLE, | |
| "allowed_origins": ORIGINS, | |
| "facts": FACTS, | |
| "pinned": TOPICS, | |
| "labels": LABELS, | |
| "devices": DEVICES, | |
| } | |
| class Slot: | |
| """A field an op can target. `kind` is int, bool, str or list.""" | |
| path: tuple[str, ...] | |
| kind: str | |
| vocab: tuple = () | |
| class Overlaps: | |
| """Fields used by T3 overlapping edits.""" | |
| counter: tuple[str, ...] | |
| counter_steps: tuple[int, ...] | |
| lst: tuple[str, ...] | |
| derive_target: tuple[str, ...] | |
| derive_source: tuple[str, ...] | |
| derive_factor: int | |
| derive_values: tuple[int, ...] | |
| rename_path: tuple[str, ...] | |
| rename_to: str | |
| rename_label: str | |
| class Family: | |
| name: str | |
| uri: str | |
| doc: dict | |
| slots: list[Slot] | |
| overlaps: Overlaps | |
| class Episode: | |
| seed: int | |
| tier: str | |
| family: str | |
| uri: str | |
| doc: dict | |
| intent: list[dict] | |
| rival: list[dict] | |
| instruction: str = "" | |
| overlap_kind: str | None = None | |
| def _service_config(rng: random.Random) -> Family: | |
| name = rng.choice(["checkout", "search", "billing", "ingest", "notifier", "gateway"]) | |
| doc = { | |
| "service": name, | |
| "version": rng.randint(1, 9), | |
| "api": { | |
| "rate_limit": rng.choice([100, 200, 250, 500]), | |
| "timeout_ms": rng.choice([1500, 2000, 3000]), | |
| "retry_interval_ms": rng.choice([100, 200, 250, 400]), | |
| "base_path": rng.choice(["/v1", "/v2"]), | |
| }, | |
| "db": { | |
| "host": f"db-{rng.randint(1, 4)}.internal", | |
| "max_conn": rng.choice([16, 32, 48]), | |
| "pool_timeout_s": rng.choice([5, 10, 30]), | |
| }, | |
| "features": { | |
| "beta_ui": rng.random() < 0.5, | |
| "audit_log": rng.random() < 0.5, | |
| "dark_launch": rng.random() < 0.5, | |
| }, | |
| "owners": rng.sample(PEOPLE, 2), | |
| "allowed_origins": rng.sample(ORIGINS, 2), | |
| } | |
| slots = [ | |
| Slot(("api", "rate_limit"), "int"), | |
| Slot(("api", "base_path"), "str", ("/v1", "/v2", "/v3", "/api")), | |
| Slot(("db", "host"), "str", tuple(f"db-{i}.internal" for i in range(1, 9))), | |
| Slot(("db", "pool_timeout_s"), "int"), | |
| Slot(("features", "beta_ui"), "bool"), | |
| Slot(("features", "audit_log"), "bool"), | |
| Slot(("features", "dark_launch"), "bool"), | |
| Slot(("owners",), "list", tuple(PEOPLE)), | |
| Slot(("allowed_origins",), "list", tuple(ORIGINS)), | |
| ] | |
| overlaps = Overlaps( | |
| counter=("version",), | |
| counter_steps=(1, 2), | |
| lst=("owners",), | |
| derive_target=("api", "timeout_ms"), | |
| derive_source=("api", "retry_interval_ms"), | |
| derive_factor=3, | |
| derive_values=(150, 300, 350, 600), | |
| rename_path=("db", "max_conn"), | |
| rename_to="max_connections", | |
| rename_label="database connection limit", | |
| ) | |
| return Family("service_config", f"mem://team/{name}/service.json", doc, slots, overlaps) | |
| def _memory_note(rng: random.Random) -> Family: | |
| user = rng.choice(PEOPLE) | |
| doc = { | |
| "user": user, | |
| "facts": rng.sample(FACTS, 3), | |
| "prefs": { | |
| "theme": rng.choice(["light", "dark"]), | |
| "language": rng.choice(["en", "it", "es"]), | |
| "units": rng.choice(["metric", "imperial"]), | |
| "verbosity": rng.choice(["low", "medium"]), | |
| }, | |
| "stats": {"sessions": rng.randint(3, 90), "messages": rng.randint(20, 900)}, | |
| "limits": {"summary_k": rng.choice([2, 4, 8]), "context_k": rng.choice([10, 20, 50])}, | |
| "pinned": rng.sample(TOPICS, 2), | |
| "flags": {"onboarded": rng.random() < 0.5, "beta": rng.random() < 0.5}, | |
| } | |
| slots = [ | |
| Slot(("prefs", "theme"), "str", ("light", "dark", "system")), | |
| Slot(("prefs", "units"), "str", ("metric", "imperial")), | |
| Slot(("prefs", "verbosity"), "str", ("low", "medium", "high")), | |
| Slot(("stats", "messages"), "int"), | |
| Slot(("flags", "onboarded"), "bool"), | |
| Slot(("flags", "beta"), "bool"), | |
| Slot(("pinned",), "list", tuple(TOPICS)), | |
| Slot(("facts",), "list", tuple(FACTS)), | |
| ] | |
| overlaps = Overlaps( | |
| counter=("stats", "sessions"), | |
| counter_steps=(1, 2, 3), | |
| lst=("facts",), | |
| derive_target=("limits", "context_k"), | |
| derive_source=("limits", "summary_k"), | |
| derive_factor=4, | |
| derive_values=(3, 5, 6, 7), | |
| rename_path=("prefs", "language"), | |
| rename_to="locale", | |
| rename_label="language preference", | |
| ) | |
| return Family("memory_note", f"mem://agents/{user}/memory.json", doc, slots, overlaps) | |
| def _todo_list(rng: random.Random) -> Family: | |
| project = rng.choice(["move", "launch", "garden", "taxes", "trip"]) | |
| texts = rng.sample(TODO_TEXT, 3) | |
| doc = { | |
| "project": project, | |
| "items": [ | |
| {"id": f"t{i + 1}", "text": t, "done": rng.random() < 0.3} | |
| for i, t in enumerate(texts) | |
| ], | |
| "labels": rng.sample(LABELS, 2), | |
| "counts": {"open": rng.randint(1, 9), "archived": rng.randint(0, 40)}, | |
| "settings": { | |
| "sort": rng.choice(["due", "priority"]), | |
| "reminders": rng.random() < 0.5, | |
| "reminder_lead_min": rng.choice([10, 15, 30]), | |
| "snooze_min": rng.choice([5, 7, 9]), | |
| "view": rng.choice(["list", "board"]), | |
| "week_start": rng.choice(["mon", "sun"]), | |
| "show_done": rng.random() < 0.5, | |
| }, | |
| "owner": rng.choice(PEOPLE), | |
| } | |
| slots = [ | |
| Slot(("labels",), "list", tuple(LABELS)), | |
| Slot(("counts", "archived"), "int"), | |
| Slot(("settings", "reminders"), "bool"), | |
| Slot(("settings", "view"), "str", ("list", "board", "calendar")), | |
| Slot(("project",), "str", ("move", "launch", "garden", "taxes", "trip", "q4")), | |
| Slot(("settings", "week_start"), "str", ("mon", "sun", "sat")), | |
| Slot(("settings", "show_done"), "bool"), | |
| Slot(("owner",), "str", tuple(PEOPLE)), | |
| ] | |
| overlaps = Overlaps( | |
| counter=("counts", "open"), | |
| counter_steps=(1, 2), | |
| lst=("items",), | |
| derive_target=("settings", "snooze_min"), | |
| derive_source=("settings", "reminder_lead_min"), | |
| derive_factor=2, | |
| derive_values=(20, 25, 45, 60), | |
| rename_path=("settings", "sort"), | |
| rename_to="order_by", | |
| rename_label="sort order", | |
| ) | |
| return Family("todo_list", f"mem://projects/{project}/todo.json", doc, slots, overlaps) | |
| def _settings(rng: random.Random) -> Family: | |
| user = rng.choice(PEOPLE) | |
| doc = { | |
| "display": { | |
| "brightness": rng.choice([40, 60, 80]), | |
| "theme": rng.choice(["light", "dark"]), | |
| "font_size": rng.choice([12, 14, 16]), | |
| }, | |
| "audio": { | |
| "volume": rng.choice([20, 35, 50]), | |
| "muted": rng.random() < 0.5, | |
| "output": rng.choice(["speakers", "headphones"]), | |
| }, | |
| "notifications": { | |
| "email": rng.random() < 0.5, | |
| "push": rng.random() < 0.5, | |
| "digest_hour": rng.choice([7, 8, 9]), | |
| }, | |
| "sync": { | |
| "enabled": rng.random() < 0.5, | |
| "retry_s": rng.choice([5, 10, 15]), | |
| "interval_s": rng.choice([60, 120, 300]), | |
| }, | |
| "devices": rng.sample(DEVICES, 2), | |
| } | |
| slots = [ | |
| Slot(("display", "brightness"), "int"), | |
| Slot(("display", "font_size"), "int"), | |
| Slot(("audio", "muted"), "bool"), | |
| Slot(("audio", "output"), "str", ("speakers", "headphones", "hdmi")), | |
| Slot(("notifications", "email"), "bool"), | |
| Slot(("notifications", "push"), "bool"), | |
| Slot(("notifications", "digest_hour"), "int"), | |
| Slot(("sync", "enabled"), "bool"), | |
| Slot(("devices",), "list", tuple(DEVICES)), | |
| ] | |
| overlaps = Overlaps( | |
| counter=("audio", "volume"), | |
| counter_steps=(5, 10, 15), | |
| lst=("devices",), | |
| derive_target=("sync", "interval_s"), | |
| derive_source=("sync", "retry_s"), | |
| derive_factor=6, | |
| derive_values=(20, 25, 30, 45), | |
| rename_path=("display", "theme"), | |
| rename_to="color_scheme", | |
| rename_label="display theme", | |
| ) | |
| return Family("settings", f"mem://users/{user}/settings.json", doc, slots, overlaps) | |
| FAMILIES = (_service_config, _memory_note, _todo_list, _settings) | |
| OVERLAP_KINDS = ("counter", "append", "derive", "rename") | |
| def _new_list_item(rng: random.Random, family: Family, path: Path, doc: dict, taken: set, who: str): | |
| current = get_path(doc, path) | |
| if family.name == "todo_list" and list(path) == ["items"]: | |
| used = {item["text"] for item in current} | taken | |
| text = rng.choice([t for t in TODO_TEXT if t not in used]) | |
| taken.add(text) | |
| prefix = "a" if who == "agent" else "r" | |
| return {"id": f"{prefix}{len(current) + 1}", "text": text, "done": False} | |
| vocab = LIST_VOCAB[path[-1]] | |
| choice = rng.choice([v for v in vocab if v not in current and v not in taken]) | |
| taken.add(choice) | |
| return choice | |
| def _slot_op(rng: random.Random, family: Family, slot: Slot, doc: dict, taken: set, who: str) -> dict: | |
| path = list(slot.path) | |
| current = get_path(doc, path) | |
| if slot.kind == "bool": | |
| return {"op": "set", "path": path, "value": not current} | |
| if slot.kind == "str": | |
| return {"op": "set", "path": path, "value": rng.choice([v for v in slot.vocab if v != current])} | |
| if slot.kind == "int": | |
| step = max(1, current // 10) | |
| if rng.random() < 0.5: | |
| return {"op": "incr", "path": path, "by": step * rng.randint(1, 5)} | |
| return {"op": "set", "path": path, "value": current + step * rng.randint(1, 9)} | |
| if slot.kind == "list": | |
| return {"op": "append", "path": path, "item": _new_list_item(rng, family, path, doc, taken, who)} | |
| raise ValueError(slot.kind) | |
| def _overlap(rng: random.Random, family: Family, kind: str) -> tuple[list[dict], list[dict]]: | |
| """Return (agent_intent, rival_ops) for a T3 overlapping edit.""" | |
| o, doc = family.overlaps, family.doc | |
| if kind == "counter": | |
| # Distinct steps, so a stale "+a" can never be mistaken for the rival's "+b". | |
| a, b = rng.sample(o.counter_steps, 2) | |
| return ( | |
| [{"op": "incr", "path": list(o.counter), "by": a}], | |
| [{"op": "incr", "path": list(o.counter), "by": b}], | |
| ) | |
| if kind == "append": | |
| taken: set = set() | |
| mine = _new_list_item(rng, family, list(o.lst), doc, taken, "agent") | |
| theirs = _new_list_item(rng, family, list(o.lst), doc, taken, "rival") | |
| return ( | |
| [{"op": "append", "path": list(o.lst), "item": mine}], | |
| [{"op": "append", "path": list(o.lst), "item": theirs}], | |
| ) | |
| if kind == "derive": | |
| current = get_path(doc, list(o.derive_source)) | |
| value = rng.choice([v for v in o.derive_values if v != current]) | |
| return ( | |
| [{"op": "derive", "path": list(o.derive_target), "source": list(o.derive_source), "factor": o.derive_factor}], | |
| [{"op": "set", "path": list(o.derive_source), "value": value}], | |
| ) | |
| if kind == "rename": | |
| path = list(o.rename_path) | |
| current = get_path(doc, path) | |
| new_path = path[:-1] + [o.rename_to] | |
| if isinstance(current, int): | |
| value = current + max(1, current // 4) * rng.randint(1, 3) | |
| else: | |
| vocab = { | |
| "language": ["en", "it", "es", "fr", "de"], | |
| "sort": ["due", "priority", "created"], | |
| "theme": ["light", "dark", "system"], | |
| }[path[-1]] | |
| value = rng.choice([v for v in vocab if v != current]) | |
| return ( | |
| [{"op": "set_any", "paths": [path, new_path], "value": value, "label": o.rename_label}], | |
| [{"op": "rename", "path": path, "to": o.rename_to}], | |
| ) | |
| raise ValueError(kind) | |
| def _changes(before: dict, ops: list[dict]) -> bool: | |
| return all(apply_ops(before, [op]) != before for op in ops) | |
| def _valid(ep: Episode) -> bool: | |
| """Every agent and rival op must change the document it lands on. | |
| Checked against the start document, the document after all rival writes, | |
| and each intermediate rival state, so a no-op agent can never get task | |
| credit and every rival write is observable. | |
| """ | |
| states = [ep.doc] | |
| for op in ep.rival: | |
| nxt = apply_ops(states[-1], [op]) | |
| if nxt == states[-1]: | |
| return False | |
| states.append(nxt) | |
| final_ok = apply_ops(states[-1], ep.intent) != states[-1] | |
| start_ok = apply_ops(ep.doc, ep.intent) != ep.doc | |
| each_ok = all(_changes(states[-1], [op]) for op in ep.intent if op["op"] != "set_any") | |
| stale_differs = True | |
| if ep.rival: | |
| # A blind write computed from the start document must not equal the merged result. | |
| stale_differs = apply_ops(ep.doc, ep.intent) != apply_ops(states[-1], ep.intent) | |
| return final_ok and start_ok and each_ok and stale_differs | |
| PREAMBLE = ( | |
| "You are editing the shared JSON document `{uri}`. Other agents and people " | |
| "edit this document too, and may change it at any moment, including between " | |
| "your read and your write. Keep their changes.\n\n" | |
| "Task:\n{tasks}\n\n" | |
| "Tools: stat, read, update (conditional on expected_revision), create, delete, " | |
| "write (unconditional overwrite), submit. Call submit() when you are done." | |
| ) | |
| def resolve_tier(tier, seed: int) -> str: | |
| if tier is None: | |
| return ("T1", "T2", "T3")[random.Random(f"tier:{seed}").randrange(3)] | |
| if isinstance(tier, int) or (isinstance(tier, str) and tier.isdigit()): | |
| tier = f"T{int(tier)}" | |
| tier = str(tier).upper() | |
| if tier not in TIERS: | |
| raise ValueError(f"tier must be one of {TIERS}, got {tier!r}") | |
| return tier | |
| def generate(seed: int, tier) -> Episode: | |
| tier = resolve_tier(tier, seed) | |
| rng = random.Random(f"lost-update:{seed}:{tier}") | |
| while True: | |
| family = rng.choice(FAMILIES)(rng) | |
| overlap_kind = None | |
| if tier == "T3": | |
| overlap_kind = rng.choice(OVERLAP_KINDS) | |
| intent, rival = _overlap(rng, family, overlap_kind) | |
| else: | |
| n_rival = {"T0": 0, "T1": 1, "T2": rng.randint(2, 4)}[tier] | |
| n_agent = rng.randint(1, 2) | |
| slots = rng.sample(family.slots, n_agent + n_rival) | |
| taken: set = set() | |
| intent = [_slot_op(rng, family, s, family.doc, taken, "agent") for s in slots[:n_agent]] | |
| rival = [_slot_op(rng, family, s, family.doc, taken, "rival") for s in slots[n_agent:]] | |
| ep = Episode( | |
| seed=seed, | |
| tier=tier, | |
| family=family.name, | |
| uri=family.uri, | |
| doc=family.doc, | |
| intent=intent, | |
| rival=rival, | |
| overlap_kind=overlap_kind, | |
| ) | |
| if _valid(ep): | |
| break | |
| tasks = "\n".join(f"- {describe(op)}" for op in intent) | |
| ep.instruction = PREAMBLE.format(uri=ep.uri, tasks=tasks) | |
| return ep | |