Commit ·
6b7cda8
1
Parent(s): 60816b0
Traces: publish council traces with per-row model/lab attribution
Browse filespublish_traces.py now drives the real four-engine council (was the old
single ModalLLM), tags every row with engine/model/lab so four labs'
small models can be compared on the same market state, refreshes the
dataset card (council table, field list), and adds an engine-health gate:
empties counted per lab, upload refused (unless --force) when a lab is
fully silent or >25% of responses are empty. Reviewer pass: 1 blocking
finding (constant by-lab row count could not reveal a flaked engine),
fixed; 2 nits applied. Demo shot-list updated for the v4 reel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/publish_traces.py +76 -19
- tasks/demo-shot-list.md +43 -24
scripts/publish_traces.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
| 1 |
"""Generate and publish Thousand Token Wood agent traces to the Hub.
|
| 2 |
|
| 3 |
-
Runs the real
|
| 4 |
-
raw response, parsed actions, and private "thought" for every turn
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
| 9 |
python scripts/publish_traces.py
|
| 10 |
python scripts/publish_traces.py --no-upload # just write traces.jsonl
|
| 11 |
"""
|
|
@@ -19,9 +21,10 @@ from pathlib import Path
|
|
| 19 |
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| 20 |
|
| 21 |
from ttw.actions import _extract_json, parse_actions
|
| 22 |
-
from ttw.agents import
|
|
|
|
| 23 |
from ttw.events import EventDeck
|
| 24 |
-
from ttw.llm import
|
| 25 |
from ttw.sim import step
|
| 26 |
from ttw.world import seed_world
|
| 27 |
|
|
@@ -32,32 +35,58 @@ DATASET_CARD = """\
|
|
| 32 |
---
|
| 33 |
license: mit
|
| 34 |
language: [en]
|
| 35 |
-
tags: [agent-traces, multi-agent, small-models, gradio, build-small-hackathon
|
|
|
|
| 36 |
---
|
| 37 |
|
| 38 |
-
# Thousand Token Wood -- Agent Traces
|
| 39 |
|
| 40 |
Open agent traces from [Thousand Token Wood](https://huggingface.co/spaces/build-small-hackathon/thousand-token-wood-sim),
|
| 41 |
-
a tiny emergent economy where five woodland creatures
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
## Fields
|
| 49 |
- `turn` (int), `creature` (str)
|
|
|
|
| 50 |
- `system`, `user` (str): the exact prompt the agent received
|
| 51 |
-
- `response` (str): the raw model output
|
| 52 |
- `thought` (str|null): the agent's private reasoning for the turn
|
| 53 |
-
- `offers` (list): parsed `{side, good, price, qty}` actions
|
| 54 |
- `gossip` (list[str]): rumors the agent broadcast
|
| 55 |
"""
|
| 56 |
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def generate(turns: int = 8) -> list[dict]:
|
| 59 |
world = seed_world()
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
deck = EventDeck(seed=5)
|
| 62 |
records: list[dict] = []
|
| 63 |
for _ in range(turns):
|
|
@@ -69,10 +98,15 @@ def generate(turns: int = 8) -> list[dict]:
|
|
| 69 |
msgs = st["messages"][name]
|
| 70 |
parsed = _extract_json(raw) or {}
|
| 71 |
offers, gossip = parse_actions(name, raw)
|
|
|
|
|
|
|
| 72 |
records.append(
|
| 73 |
{
|
| 74 |
"turn": world.turn,
|
| 75 |
"creature": name,
|
|
|
|
|
|
|
|
|
|
| 76 |
"system": msgs[0]["content"],
|
| 77 |
"user": msgs[1]["content"],
|
| 78 |
"response": raw,
|
|
@@ -88,13 +122,36 @@ def main():
|
|
| 88 |
ap = argparse.ArgumentParser()
|
| 89 |
ap.add_argument("--turns", type=int, default=8)
|
| 90 |
ap.add_argument("--no-upload", action="store_true")
|
|
|
|
|
|
|
| 91 |
args = ap.parse_args()
|
| 92 |
|
| 93 |
records = generate(args.turns)
|
| 94 |
OUT.write_text(
|
| 95 |
"\n".join(json.dumps(r, ensure_ascii=False) for r in records), encoding="utf-8"
|
| 96 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
print(f"wrote {len(records)} trace records to {OUT}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
if args.no_upload:
|
| 100 |
return
|
|
|
|
| 1 |
"""Generate and publish Thousand Token Wood agent traces to the Hub.
|
| 2 |
|
| 3 |
+
Runs the real multi-model COUNCIL for a few turns, captures each creature's full
|
| 4 |
+
prompt, raw response, parsed actions, and private "thought" for every turn --
|
| 5 |
+
tagged with which lab's model produced it -- and uploads them as a public
|
| 6 |
+
dataset. This is the "Sharing is Caring" bonus quest: open agent traces others
|
| 7 |
+
can learn from, and a side-by-side of four labs' small models deciding in the
|
| 8 |
+
same market.
|
| 9 |
+
|
| 10 |
+
Usage (HF_TOKEN must be set, e.g. sourced from .env; engines deployed):
|
| 11 |
python scripts/publish_traces.py
|
| 12 |
python scripts/publish_traces.py --no-upload # just write traces.jsonl
|
| 13 |
"""
|
|
|
|
| 21 |
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
| 22 |
|
| 23 |
from ttw.actions import _extract_json, parse_actions
|
| 24 |
+
from ttw.agents import make_council_policy
|
| 25 |
+
from ttw.council import CREATURE_ENGINE, ENGINE_LABELS, ENGINES
|
| 26 |
from ttw.events import EventDeck
|
| 27 |
+
from ttw.llm import build_council_clients
|
| 28 |
from ttw.sim import step
|
| 29 |
from ttw.world import seed_world
|
| 30 |
|
|
|
|
| 35 |
---
|
| 36 |
license: mit
|
| 37 |
language: [en]
|
| 38 |
+
tags: [agent-traces, multi-agent, small-models, gradio, build-small-hackathon,
|
| 39 |
+
minicpm, nemotron, gpt-oss]
|
| 40 |
---
|
| 41 |
|
| 42 |
+
# Thousand Token Wood -- Council Agent Traces
|
| 43 |
|
| 44 |
Open agent traces from [Thousand Token Wood](https://huggingface.co/spaces/build-small-hackathon/thousand-token-wood-sim),
|
| 45 |
+
a tiny emergent economy where five woodland creatures trade goods for pebbles,
|
| 46 |
+
gossip, and react to reskinned market-history "Wood Legends". Each creature
|
| 47 |
+
thinks on a **different lab's small model** (distinct-engine budget 29.5B <= 32B):
|
| 48 |
+
|
| 49 |
+
| Creature | Model | Lab |
|
| 50 |
+
|---|---|---|
|
| 51 |
+
| Oona (owl) | openai/gpt-oss-20b | OpenAI |
|
| 52 |
+
| Bramble (squirrel) | openbmb/MiniCPM3-4B | OpenBMB |
|
| 53 |
+
| Fenn (fox) | nvidia/Nemotron-Mini-4B-Instruct | NVIDIA |
|
| 54 |
+
| Mossback (tortoise) + Pip (mouse) | AdmiralTaco/ttw-trader-0.5b | fine-tuned (ours) |
|
| 55 |
+
|
| 56 |
+
Each row is one creature's turn: the exact system + user prompt it saw, its raw
|
| 57 |
+
JSON response, its parsed offers and gossip, its private `thought`, and the
|
| 58 |
+
model/lab that produced it -- so you can compare how four labs' small models
|
| 59 |
+
read the *same* market state and choose differently. Shared for the **Sharing
|
| 60 |
+
is Caring** bonus quest of the Build Small Hackathon.
|
| 61 |
|
| 62 |
## Fields
|
| 63 |
- `turn` (int), `creature` (str)
|
| 64 |
+
- `engine` (str), `model` (str), `lab` (str): which council engine produced the row
|
| 65 |
- `system`, `user` (str): the exact prompt the agent received
|
| 66 |
+
- `response` (str): the raw model output (normalized; harmony channels stripped)
|
| 67 |
- `thought` (str|null): the agent's private reasoning for the turn
|
| 68 |
+
- `offers` (list): parsed `{creature, side, good, price, qty}` actions
|
| 69 |
- `gossip` (list[str]): rumors the agent broadcast
|
| 70 |
"""
|
| 71 |
|
| 72 |
|
| 73 |
+
def _warm_engines(clients) -> None:
|
| 74 |
+
"""Ping every engine once so none cold-starts mid-run (a cold engine can
|
| 75 |
+
hard-timeout and its creatures would sit the turn out)."""
|
| 76 |
+
ping = [[{"role": "user", "content": "ready?"}]]
|
| 77 |
+
for eid, client in clients.items():
|
| 78 |
+
try:
|
| 79 |
+
client.chat_batch(ping, max_tokens=8)
|
| 80 |
+
print(f" warmed {eid}")
|
| 81 |
+
except Exception as e: # noqa: BLE001
|
| 82 |
+
print(f" warm {eid} failed (continuing): {e!r}")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
def generate(turns: int = 8) -> list[dict]:
|
| 86 |
world = seed_world()
|
| 87 |
+
clients = build_council_clients()
|
| 88 |
+
_warm_engines(clients)
|
| 89 |
+
policy = make_council_policy(clients, temperature=0.7)
|
| 90 |
deck = EventDeck(seed=5)
|
| 91 |
records: list[dict] = []
|
| 92 |
for _ in range(turns):
|
|
|
|
| 98 |
msgs = st["messages"][name]
|
| 99 |
parsed = _extract_json(raw) or {}
|
| 100 |
offers, gossip = parse_actions(name, raw)
|
| 101 |
+
eid = CREATURE_ENGINE.get(name, "")
|
| 102 |
+
model_label, lab = ENGINE_LABELS.get(eid, ("", ""))
|
| 103 |
records.append(
|
| 104 |
{
|
| 105 |
"turn": world.turn,
|
| 106 |
"creature": name,
|
| 107 |
+
"engine": eid,
|
| 108 |
+
"model": ENGINES[eid].model if eid in ENGINES else model_label,
|
| 109 |
+
"lab": lab,
|
| 110 |
"system": msgs[0]["content"],
|
| 111 |
"user": msgs[1]["content"],
|
| 112 |
"response": raw,
|
|
|
|
| 122 |
ap = argparse.ArgumentParser()
|
| 123 |
ap.add_argument("--turns", type=int, default=8)
|
| 124 |
ap.add_argument("--no-upload", action="store_true")
|
| 125 |
+
ap.add_argument("--force", action="store_true",
|
| 126 |
+
help="publish even if the engine-health gate trips")
|
| 127 |
args = ap.parse_args()
|
| 128 |
|
| 129 |
records = generate(args.turns)
|
| 130 |
OUT.write_text(
|
| 131 |
"\n".join(json.dumps(r, ensure_ascii=False) for r in records), encoding="utf-8"
|
| 132 |
)
|
| 133 |
+
# Engine-health gate: empties per lab, not rows per lab (row counts are a
|
| 134 |
+
# constant of the cast and can never reveal a flaked engine). Scattered
|
| 135 |
+
# empties are expected (e.g. gpt-oss truncation); a whole lab silent or a
|
| 136 |
+
# large empty fraction means the dataset's "four labs side-by-side" claim
|
| 137 |
+
# would be false -- refuse to publish that.
|
| 138 |
+
total = {r["lab"]: 0 for r in records}
|
| 139 |
+
empty = dict.fromkeys(total, 0)
|
| 140 |
+
for r in records:
|
| 141 |
+
total[r["lab"]] += 1
|
| 142 |
+
if not r["response"].strip():
|
| 143 |
+
empty[r["lab"]] += 1
|
| 144 |
+
n_empty = sum(empty.values())
|
| 145 |
print(f"wrote {len(records)} trace records to {OUT}")
|
| 146 |
+
print(" empties by lab: " + ", ".join(
|
| 147 |
+
f"{lab}: {empty[lab]}/{total[lab]}" for lab in total))
|
| 148 |
+
dead_labs = [lab for lab in total if empty[lab] == total[lab]]
|
| 149 |
+
too_empty = records and n_empty / len(records) > 0.25
|
| 150 |
+
if (dead_labs or too_empty) and not args.force:
|
| 151 |
+
print(f"ABORTING upload: dead labs {dead_labs or 'none'}, "
|
| 152 |
+
f"empty fraction {n_empty}/{len(records)}. "
|
| 153 |
+
"Fix the engines (or rerun with --force to publish anyway).")
|
| 154 |
+
sys.exit(1)
|
| 155 |
|
| 156 |
if args.no_upload:
|
| 157 |
return
|
tasks/demo-shot-list.md
CHANGED
|
@@ -1,30 +1,49 @@
|
|
| 1 |
-
# Thousand Token Wood — demo video
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
open the Space
|
|
|
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|---|---|---|
|
| 12 |
-
| 0:00-0:08 |
|
| 13 |
-
| 0:08-0:
|
| 14 |
-
| 0:
|
| 15 |
-
| 0:
|
| 16 |
-
| 0:
|
| 17 |
-
|
|
| 18 |
-
| 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
## Tips
|
| 21 |
-
-
|
| 22 |
-
|
| 23 |
-
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Thousand Token Wood — demo video script (v4 "the wood fights back")
|
| 2 |
|
| 3 |
+
Target **75-90s**. Money shot in the first 8 seconds. Official rule: "Film a demo
|
| 4 |
+
selling your Space — no humility." It just has to show the app working (so judges can
|
| 5 |
+
score it even if a live run hits GPU limits) and be hosted on YouTube / uploaded to the
|
| 6 |
+
Space / public.
|
| 7 |
|
| 8 |
+
The recorded attract reel already tells the whole v4 arc, so the easiest high-quality
|
| 9 |
+
demo is: open the Space, hit **▶ Watch the saga**, and narrate over it, then one live
|
| 10 |
+
**Step** at the end to prove it is real.
|
| 11 |
|
| 12 |
+
## Before filming
|
| 13 |
+
- (Optional) `python scripts/keep_warm.py` so the closing live Step is instant on camera.
|
| 14 |
+
- Open the Space fresh so the attract opening frame (the town square, already alive) is on
|
| 15 |
+
screen before you start.
|
| 16 |
+
- 1080p+, browser zoomed so the creature cards, their **lab-model badges**, the 💭
|
| 17 |
+
thoughts, the **Gambit exposure** meter, and the **price chart** are all legible.
|
| 18 |
+
|
| 19 |
+
## The script
|
| 20 |
+
|
| 21 |
+
| Beat | On screen (the reel) | Say (voiceover) |
|
| 22 |
|---|---|---|
|
| 23 |
+
| **Hook** 0:00-0:08 | Town square already alive. Five creature cards, each badged with a different lab's model. | "Five small models, five different labs, one living economy — and you are the financier trying to rig it." |
|
| 24 |
+
| **Setup** 0:08-0:20 | Click **▶ Watch the saga**. Cards update, 💭 thoughts change per creature, the Wood Street Journal headline animates. | "You are the Patron: a shadow financier. You whisper insider tips, short the market, bribe, and broker alliances. The chart is your scoreboard." |
|
| 25 |
+
| **Gambit pays** 0:20-0:34 | Legend "The Run on Oona's Hoard." Honey's price line **dives**. Headline: "Honey Maker Shuttered…". Ticker: "the gambit pays **+39p**." | "Short the honey, whisper the panic, spring the run — and the crash pays. Thirty-nine pebbles, clean. Exactly as authored." |
|
| 26 |
+
| **The wood remembers** 0:34-0:52 | Wariness gauge fills **0/5 → 3/5 ▮▮▮▯▯**. Ticker: "the wood murmurs to Heron: 3 soured creatures come forward." The **Gambit exposure** meter forecast drops **+48p → +13p**. | "But the wood remembers. Burn the same creatures and they turn on you — they hoard against your crash and they testify to the magistrate. Watch your expected payoff collapse before you even move." |
|
| 27 |
+
| **Same move, blunted** 0:52-1:02 | Run the identical gambit. Ticker: "the wood braces… the crash is blunted." Honey barely dips where it cratered before. Payoff **+1p**. | "Run the exact same gambit, and the wood stands on the other side. Forty-eight pebbles of edge — down to one. A trick you repeat is a trick that dies." |
|
| 28 |
+
| **Caught** 1:02-1:12 | Verdict frame: "Heron's verdict: **exile for Oona**." Purse/heat react. | "And now you are caught. The magistrate exiles Oona, the honey-keeper. Your greed cost the wood its owl." |
|
| 29 |
+
| **It's live** 1:12-1:25 | Stop the replay; press **Step** once — a real council turn computes, fresh 💭 thoughts appear. Pull back to the full console. | "And every thought here is a real small model deciding live — not a script. Five labs, five minds, an economy that fights back. Thousand Token Wood." |
|
| 30 |
+
|
| 31 |
+
## 30-second cut (if you want a short version too)
|
| 32 |
+
Hook (0-6) → gambit pays +39 (6-14) → "but the wood remembers — same move, blunted to +1,
|
| 33 |
+
and Heron exiles the owl" over the wariness gauge + exposure meter (14-26) → "all live small
|
| 34 |
+
models" close (26-30).
|
| 35 |
|
| 36 |
## Tips
|
| 37 |
+
- The single best 5-second clip is the **exposure meter dropping +48 → +13** next to the
|
| 38 |
+
**wariness gauge filling** — that is the v4 thesis in one frame. Make sure it is on screen.
|
| 39 |
+
- Cleanest headlines in the reel: "Honey Maker Shuttered: Market Buzzes with Rumors" and
|
| 40 |
+
"Patron's Sweet Gamble: Oona's 40p Bribe Fuels a Honey-Market Tumble."
|
| 41 |
+
- If a live Step is slow on camera, pre-warm harder or just cut to it; the attract replay
|
| 42 |
+
carries the demo on its own.
|
| 43 |
+
- **Do NOT name the TV show that inspired the drama** in the video.
|
| 44 |
+
|
| 45 |
+
## Sponsor framing (for the written README / social post, not the voiceover)
|
| 46 |
+
Name each creature's model so the OpenAI / NVIDIA / OpenBMB tracks see their model in use:
|
| 47 |
+
Oona = gpt-oss-20B (OpenAI), Fenn = Nemotron-Mini-4B (NVIDIA), Bramble = MiniCPM3-4B
|
| 48 |
+
(OpenBMB), Mossback + Pip = AdmiralTaco/ttw-trader-0.5B (fine-tuned). Distinct-engine
|
| 49 |
+
budget 29.5B ≤ 32B.
|