Spaces:
Running
Running
| # 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": "<uuid>", | |
| "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": "<static, public, in-repo>", | |
| "user_prompt": "A parent needs help...\n\nThe latest thing the parent is asking about: <redacted, see request.situation_length>", | |
| "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. | |