# Zero Proof Labs workflow: agent, datasets, profile, publish

For coding agents working on behalf of an engineer doing AI research.
Everything on the platform hangs off an agent. Six calls.

## 1. Sign in

```bash
pip install zeroproof
zeroproof login                            # existing account, one click in the browser
zeroproof signup --email you@example.com   # new account, no browser (trial key until one sign-in)
```

Rules: https://zeroproofai.com/get-started-skill.md

## 2. Name the agent

An agent is a record on the account. It exists the moment a push names
it (`agent=`) or a trace arrives with `gen_ai.agent.name`.

```python
import zeroproof.simulations as zps
zps.register_agent("airline-support", description="Refunds and rebooking")   # optional
zps.agents()   # every agent: traces, sets by purpose (train/holdout/eval), public cards
```

## 3. Push train and holdout

Every dataset has a purpose: `train`, `holdout` or `eval`. A push is
train unless told otherwise. Ingested traces are eval until training
data is cut from them. There is no unassigned state.

```python
data = zps.simulate(spec="specs/airline", mode="rl", budget=200)
data.grade()
entry = data.push("airline-v3", agent="airline-support", holdout=0.2)
entry["datasetId"]               # train set
entry["holdout"]["datasetId"]    # holdout set, split by scenario_id, linked to the train set
data.push("airline-evals", agent="airline-support", purpose="eval")
zps.update_dataset("ds_...", purpose="holdout")
```

The push gate refuses an RL-shaped set that is ungraded or has no mixed
group (`gate=False` uploads as is). The simulation mode is recorded.

## 4. Read the profile before training

```python
p = zps.profile("ds_...")   # cached until the set changes; force=True recomputes
```

| key | meaning |
|---|---|
| `pass_rate` | reward ≥ 0.5 over graded rows. Near 0 or 1: no headroom. |
| `support` | mean p(1−p)/0.25 over tasks with 2+ graded rows. ≥ 0.3 trains; < 0.05 flat. |
| `mixed_tasks` / `tasks_with_repeats` | tasks with both a pass and a fail. RL needs these. |
| `rows_per_task` | repeats. Below 2 there is no group. |
| `dup_prompts` | rollouts sharing a prompt. Expected for RL, a warning for SFT. |
| `per_task` | top tasks: rows, graded, pass rate. |
| `tool_calls` | mean per row, max, top tools. |

Tell the person the verdict in one line: trainable (support ≥ 0.3 and
mixed ≥ 30%), weak signal, flat, or no headroom.

## 5. Publish

```python
zps.publish("ds_...", agent="airline-support", description="Graded refund conversations.")
zps.catalog()             # public cards by agent
rows = zps.pull("ds_...") # anyone, no key
zps.unpublish("ds_...")
```

Cards: https://zeroproofai.com/datasets

## 5b. Hugging Face, both directions

The person connects their Hugging Face account once, on any dataset page
under Platform → Datasets (Connect Hugging Face). The platform holds the
token; the SDK never sees it. If a push says `hf_not_connected`, send them
there.

```python
zps.hf_status()                                             # {"connected", "username", "namespaces"}
hf = zps.hf_publish("ds_...", repo="airline-refunds", wait=True)   # rows -> a dataset repo they own
hf["url"], hf["commit"], hf["tag"]                          # every push is one commit, tagged zp-<dataset id>
zps.hf_publish_run("run_...", private=True)                 # a finished run's LoRA adapter -> a model repo
row = zps.import_hf("ns/name", split="test", purpose="eval")   # any Hub split -> their account, then zps.profile(row["datasetId"])
```

One repo holds one split per purpose (train, holdout, eval). Pushing a new
cut into the same split replaces it and the commit message carries the
delta; `zeroproof.json` in the repo keeps the history. `repo` must be
`namespace/name` for imports. Public repos import without a connection.

## 6. Evals come from traces

One trace per case, tagged with the eval set, over OpenTelemetry:
https://zeroproofai.com/traces-skill.md. The Eval section of Datasets
shows each set with its pass rate and the change since the last run.

## 7. Purge

Permanent. Always dry-run first and show the counts to the person.

```bash
zeroproof purge --agent demo-agent --dry-run
zeroproof purge --agent demo-agent --yes
zeroproof purge --empty --max-rows 2 --yes
```

Python: `zps.purge_agent("demo-agent", dry_run=True)`, `zps.delete_empty_datasets(max_rows=2)`.

## Routes (X-Api-Key)

```
GET/POST /agents          GET/DELETE /agents/{slug}
POST /datasets {name, agent, purpose, mode}  -> uploadUrl; POST /datasets/{id}/finalize
POST /datasets/{id}/meta {purpose|mode|agent|description}
GET  /datasets/{id}/profile   GET /datasets/{id}/preview
POST /datasets/{id}/publish   POST /datasets/{id}/unpublish
GET  /hf/me   POST /datasets/{id}/hf-publish   POST /runs/{id}/hf-publish   POST /datasets/import-hf {repo, split, purpose}
GET  /catalog   GET /catalog/{id}   GET /catalog/{id}/download     (no key)
DELETE /datasets/{id}   DELETE /traces/{id}
```
