# Fabella Anonymized Agent Traces When the public publisher is enabled, one anonymized row per request lands at [`build-small-hackathon/fabella-traces`](https://huggingface.co/datasets/build-small-hackathon/fabella-traces). The dataset repo is public and contains the live card, the publisher schema, and 5 seed rows for the data viewer. The Space publishes to it only when `FABELLA_SHARE_TRACES=1` is set on the Space (default `0`); with the flag off, rows only live in the per-parent bucket and are exportable via the Space's **Settings → Download my history** button. This file is a local copy of the Hugging Face dataset card for Fabella's anonymized agent-trace publication. The live card on the Hub is the source of truth; this file is a convenience for reviewers reading the source repo. ## Why this exists Fabella is a small Hugging Face Space that drafts short, kind, age-appropriate explanations a parent can use to talk to their child about a hard thing (a hospitalization, a move, a pet dying). The first version is generated by `google/gemma-4-E4B-it`; a `nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16` judge scores it against a six-criterion rubric; the drafter revises if needed. An optional read-aloud uses `openbmb/VoxCPM2`. The dataset captures the full ReAct loop for every successful generation, with PII removed before the row leaves the Space. It's the **Sharing is Caring** merit-badge artifact for the [Build Small Hackathon](https://huggingface.co/spaces/build-small-hackathon/README) submission. ## Where it lives - **Live dataset (when enabled):** https://huggingface.co/datasets/build-small-hackathon/fabella-traces (default) - **Personal-namespace fallback:** set `FABELLA_TRACE_REPO=Kiy-K/fabella-traces` to publish to the maker's personal dataset instead. Used for local dev where the Space's HF_TOKEN can't create in the org namespace. - **Default for this demo:** the publisher is OFF (`FABELLA_SHARE_TRACES=0`); data lives in the per-parent bucket and is exportable via the Space's **Settings → Download my history** button. The dataset repo is created on first publish by `trace.py::_ensure_repo` via `HfApi.create_repo(exist_ok=True)`, so a fresh deployment does not require a one-time setup step. ## Schema (one row per request, when publishing is enabled) ```jsonc { "trace_id": "", "created_at": "2026-06-14T14:04:05+00:00", "schema_version": 1, "app_version": "fabella-1", "model_versions": { "drafter": "google/gemma-4-E4B-it", "judge": "nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16" }, "request": { "age": 7, "tone": "gentle", "child_name_present": true, "child_name_redacted": "[name]", "situation_hash": "sha256:9a3ba115d57a3789", "situation_preview": "My 7yo's grandma is in the hospital.", "situation_length": 36, "history_turns": 0 }, "agent": { "system_prompt": "", "user_prompt": "A parent needs help...\n\nThe latest thing the parent is asking about: ", "messages": [ /* full LangChain ReAct trace */ ], "final_draft": { "opener": "...", "body": "...", "closer": "...", "followup": "..." } }, "judge": { "ok": true, "issues": [], "score": 0.92, "verdict": "approve", "reasoning": "good" }, "latency_ms": 1234, "tool_calls": 1 } ``` ## Anonymization The publisher in `trace.py` runs every row through five anonymization passes before the row leaves the Space: 1. **Child name** is dropped from the request and replaced with `"[name]"` in the final draft. 2. **Raw situation** is never stored. Only its SHA-256 hash, the first 60 chars as a topic preview, and its character length are kept. 3. **Freeform history turns** are replaced with role + length counts. The drafter's user prompt additionally replaces the embedded situation with a length-only marker. 4. **System prompt** is shipped in full (it's a static string in this repo, not user input). 5. **LangChain 1.x `content` lists** are normalized so list-of-blocks content is joined to its text. ## Self-export Every parent can pull their own data via **Settings → Download my history** in the running Space. The downloaded JSON bundle contains: - All chat turns stored under the parent's owner key (full, not redacted — the parent already has this in their browser). - The durable memory summary. - A `trace_publication` section that says exactly which dataset the redacted rows go to, the URL, the per-session row estimate, and the same five anonymization passes above. The download path exists for two reasons: (1) the parent can verify that what we ship to the public dataset does not include raw situation text, child name, or anything that would link a row back to them; and (2) for a 3-day hackathon demo, it sidesteps the cold-start tax of the Hub push entirely. ## Opt-out - Set `FABELLA_SHARE_TRACES=0` on the Space to kill the publisher (this is the default). - Or pass `share_trace=False` on `make_explanation` to opt out per request. The default for this demo is **off**: rows only live in the per-parent bucket, accessible via the Download button. ## Source code - `trace.py` — the source of truth for the schema and the anonymization rules. - `agent.py::_maybe_publish_trace` — the call site that builds and submits each row. - `app.py` — calls `_trace.publisher.start()` at import time, exposes `GET /api/history/download` for the parent self-export. - `app.py` `INDEX_HTML` — wires the **Download my history** button in the settings dialog.