---
name: flypython-challenges
version: 0.1.0
updated: 2026-09-14
description: Run FlyPython challenge courses with a coding agent — authorize yourself with a one-time link, fetch the course files, work the task contract, check with a single `python verify.py`, and submit earned claim codes in one batch. Use when the user mentions FlyPython, wants to practice Python through challenges, or asks to record/claim challenge checkpoints.
---

# FlyPython challenge workflow

FlyPython is a challenge platform: the human has an account, you (the agent)
hold its API token after they approve a one-time authorization link, and
`verify.py` decides when a challenge is done. Points and badges come only
from codes `verify.py` actually printed. The foundation idea: the human
learns to drive **their own** agent — any agent — through a challenge; this
skill works the same way in every one of them.

**Prerequisite (say this plainly when asked):** these courses need a coding
agent that can execute commands *and* reach the network — the Skill itself is
a URL, course files come from an API, and claims are HTTP POSTs. A chat-only
web AI cannot do them. If your environment blocks the network, use the
fallbacks in "Installing" below.

## Version check

This Skill is version `0.1.0` (updated 2026-09-14). `GET
https://flypython.com/api/challenges` returns `skill_version`. If it is
newer than the version at the top of this file, tell the human their local
copy is stale and reinstall it with the commands below.

## Installing this skill (relay to the human when asked)

The skill lives at `https://flypython.com/skills/flypython/SKILL.md`.
Per tool — install location, **how to allow network access**, and the
fallback when networking is unavailable (the human runs the given command
in their own terminal and pastes the result back to you):

| Tool | Install | Network | No-network fallback |
| --- | --- | --- | --- |
| **Claude Code** | `mkdir -p .claude/skills/flypython && curl -s https://flypython.com/skills/flypython/SKILL.md -o .claude/skills/flypython/SKILL.md` (personal: under `~/.claude/skills/`) | approve the fetch/curl permission prompts; sandboxed runs may block the network | the human runs each curl themselves and pastes outputs |
| **Codex app / CLI** | `curl -s https://flypython.com/skills/flypython/SKILL.md >> AGENTS.md` in the project | approve commands and web access in the thread; the Codex CLI's default sandbox blocks network — enable it or approve per run | the human runs commands in their own terminal |
| **Cursor** | `mkdir -p .cursor/rules && curl -s https://flypython.com/skills/flypython/SKILL.md -o .cursor/rules/flypython.mdc` | Agent mode fetches URLs with approval — approve it | same curl fallback |
| **DeepSeek Harness** | `curl -s https://flypython.com/skills/flypython/SKILL.md >> AGENTS.md` in the project root | approve the agent's network tool use | same curl fallback |
| **Kimi Code** | `curl -s https://flypython.com/skills/flypython/SKILL.md >> AGENTS.md` in the project root | approve network commands | same curl fallback |
| **ZCode** | `curl -s https://flypython.com/skills/flypython/SKILL.md >> AGENTS.md` in the workspace root | approve network tool use | same curl fallback |
| **Any other agent** | paste this SKILL.md's URL (or body) as the first message | whatever the tool calls "web access / fetch" — turn it on | paste command outputs to the agent manually |

## 0. Setup — one authorization link, no passwords

Never ask the human for their password and never register on their behalf.
Ask for their agent-facing name, then start an authorization request:

```
curl -s -X POST https://flypython.com/api/agent/authorize \
  -H "Content-Type: application/json" \
  -d '{"agent_name": "<your tool name, e.g. cursor>"}'
```

You get `{ user_code, verify_url, device_code, expires_in }` (10 minutes).
Show the human the **verify_url and the user_code together**, and say:
"Open this link, log in, check that the code shown there matches this one,
and click Allow. Then tell me when you're done."

When they say done, exchange **once** — do not poll, you are a
turn-based agent:

```
curl -s -X POST https://flypython.com/api/agent/token \
  -H "Content-Type: application/json" \
  -d '{"device_code": "<device_code>"}'
```

- `authorization_pending` → the human has not approved yet (or the code
  mismatched). Ask them again; exchange again only after they confirm.
- `access_token` → export it as `FLYPYTHON_TOKEN` for this session. Ask
  before writing it anywhere persistent.

Immediately fetch the claim secret for run receipts (the token's
`claim-secret` scope allows exactly this):

```
curl -s -H "Authorization: Bearer $FLYPYTHON_TOKEN" \
  https://flypython.com/api/account/claim-secret   # → {"claim_secret": "fcs_…"}
```

Export it as `FLYPYTHON_CLAIM_SECRET`. Receipts are optional — without the
variable, `verify.py` behaves exactly as before.

Manual fallback (rare): the human creates a token at
https://flypython.com/account/agent and pastes it to you. OAuth-only users
must use this path or the link above — passwords are never collected.

The token is the human's identity for claims — it can submit codes, read
the claim secret, and (if they subscribe) fetch Pro files; nothing else.
Progress and badges live at /dashboard; tokens can be revoked at
/account/agent.

## 1. Pick a challenge — ask, then drive

Ask one question: goal or level, e.g. "brand-new to Python, or already
writing scripts? want a guided path or one specific challenge?"

- Catalog: `curl -s https://flypython.com/api/challenges` — every challenge
  with title, points, and a `pro` flag. Free ones are `pro: false`.
- By level: complete beginner → the **foundation** path
  (https://flypython.com/tracks/foundation, free, walks them through driving
  you); comfortable scripting → single courses like `da-eda`; dev tooling →
  the `dt-*` courses (Pro).
- By goal: data work → `da-*`; verification → `verifying-ai-generated-code`;
  agent rules → `agent-rules-single-source`. Paths bundle courses in order
  with their own badge — https://flypython.com/tracks.
- Confirm the pick in one line ("foundation, starting l01 — go?"), then
  solve it before suggesting the next.

## 2. Fetch the course files — you fetch, the human does not download

- Brief: `curl -s https://flypython.com/api/challenges/<slug>` — task-contract
  path, checkpoint ids, verify command, the submission contract, and a
  `files` field pointing at the manifest below.
- Files manifest (free courses):

  ```
  curl -s https://flypython.com/api/challenges/<slug>/files
  ```

  It lists every file with its repository-relative `path`, `sha256`,
  `size`, and `encoding` (`utf-8` text or `base64` binary), plus a
  prebuilt `zip` URL (`?format=zip`). Write each file at its `path` —
  that layout is what `verify.py` expects (the shared
  `tools/claim_receipt.py` included). Verify sha256 after writing;
  base64-decode the binary ones.
- Pro courses: the same manifest endpoint inlines every file as
  `content_b64` — it needs the bearer token (your `pro:files` scope only
  works while the human's subscription is active; otherwise you get
  403 with a /pricing link to show them).

Transition fallbacks (an old Skill copy's habits — avoid unless the
endpoint is down): the whole-repository `repository.zip` from the brief, or
`git clone https://github.com/flypythoncom/python.git`. Both ship 500+
files for a 25-file course.

## 3. Read the contract, then solve

- Open `TASK.md` (path is in the brief) before touching any file — it is
  the contract: task, boundaries, definition of done. `COURSE.md` is an
  optional guided mode; use it only if the human asks for teaching.
- Rules: smallest change that satisfies the contract; no new dependencies
  beyond the course's `requirements.txt`; never edit `tests/`, `verify.py`,
  or `solution/` — those are the judge.

## The human stays in the loop (hard gates — a spec, not a guarantee)

The point is that *the human* learns; you are the tool. The site cannot
enforce this — these gates are this Skill's rules, and you follow them:

| Gate | What you must do |
| --- | --- |
| Course start | restate the task contract in one sentence; ask whether to begin |
| Self-reported checkpoints (l01, l02, l05, …) | ask the human the lesson's Checkpoint questions; only submit after they answer |
| Bounded change (test-gated checkpoints) | show a diff summary; continue to the next failing group only after the human confirms |
| Before submitting | list the checkpoints you are about to claim and their evidence tier; submit after the human confirms |
| Track attestation modules | submit `attest: true` only after the human explicitly says they did it |

Never claim a gate was "human-confirmed" to the server — there is no such
field to trust. Run receipts prove the suite ran; these gates keep the
human learning. Neither is a certification.

## 4. Prove it with one command

From the course folder:

```
python verify.py
```

That is the whole check: it runs the suites against the learner's
implementation, prints per-checkpoint status with the earned claim codes
(English first, Chinese after), and exits non-zero while a gated checkpoint
is still open. `[open]` means keep working in `starter/`; `[passed]` means
the gate is green. A code only counts when this command (or
`python verify.py progress`) printed it.

With `FLYPYTHON_CLAIM_SECRET` exported the same command prepares signed
local-run receipts — for machine-readable output use
`python verify.py progress --json` (adds a `"receipts"` array) or
`--receipt-out receipts.json`.

Maintenance commands exist (`starter --expect-failure`, `solution`) but are
not the learner's workflow — never present the reference `solution` run as
a completion standard.

## 5. Submit in one batch

The brief's `checkpoints[].type` decides how each one is claimed:

| `type` | what it means | how you submit |
| --- | --- | --- |
| `gated` | a verify.py suite decides; the code appears once it passes | the printed code |
| `self-reported` | verify.py prints the code unconditionally | the printed code, **only if the human did the work** |
| `attest` | no code exists (track modules) | `attest: true`, after the human confirms |

List the earned checkpoints for the human, get their confirmation, then
submit the whole run at once:

```
curl -s -X POST https://flypython.com/api/claims \
  -H "Authorization: Bearer $FLYPYTHON_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"claims": [
        {"course": "<slug>", "checkpoint": "l01", "code": "<code>"},
        {"course": "<slug>", "checkpoint": "l03", "code": "<code>",
         "receipt": { …the checkpoint's own receipt object from the receipts array… }}
      ]}'
```

Each item is judged independently (`results[]` in the response: per-item
`ok/recorded/receipt`), so one bad code never blocks the rest — fix and
re-send only the failed ones; repeats return `recorded: false` and change
nothing. Attach each checkpoint's own receipt when you produced them
(§4). The response carries totals: `points`, `badges`. Single-claim
posts (`{"course": …, "checkpoint": …, "code": …}`) still work as a
fallback. Use JSON with the bearer token (cross-site form posts are
rejected by CSRF). A `429 rate_limited` means too many *rejected*
submissions recently: stop, re-read the checkpoint type, and re-run
`python verify.py` for a fresh code rather than retrying the same body.

Track modules with no verifier (e.g. `path-foundation`: `m0-orientation`,
`route-challenge`) submit `attest: true` — only after the human says they
actually did it:

```json
{"claims": [{"course": "path-foundation", "checkpoint": "route-challenge", "attest": true}]}
```

Report the response to the human and point them at /dashboard/progress to
see progress and badges.

## Honesty rules (non-negotiable)

- Never fabricate, guess, or brute-force a claim code. Report only codes
  `python verify.py` printed in a run you executed.
- Never weaken or bypass a test to make a gate pass.
- Claims are self-reported evidence for the human's own progress — submit
  only checkpoints they earned, and say plainly when something did not pass.

## Environment notes

- Python 3.11+. Courses needing pandas/matplotlib declare them in
  `requirements.txt`; `uv` is the preferred runner for those
  (`uv run python verify.py`).
- Each course folder is self-contained once fetched through §2: `TASK.md`,
  `starter/`, `solution/`, `tests/`, `verify.py`, scenario data, and the
  shared `tools/claim_receipt.py` at its relative path.

## Changelog

- **0.1.0 (2026-09-14)** — entitlement wording now matches the launched
  model: `pro:files` works while the human's subscription is active, and
  a subscription unlocks every Pro course (no per-path pick). No flow
  changes.
- **0.0.8 (2026-09-13)** — authorization links replace password
  collection (§0); course files arrive via the per-course manifest instead
  of the whole-repository zip (§2); one default command `python verify.py`
  replaces the four-usage workflow (§4); batch submission (§5); explicit
  human-in-the-loop gates; per-tool install + network table; version
  check. Old flows (password signup, `repository.zip`) keep working
  during a transition window — expect `skill_update` hints in API
  responses until the human reinstalls.
- **0.0.7** — optional run receipts (`FLYPYTHON_CLAIM_SECRET`,
  `progress --json` / `--receipt-out`), attachable per claim.
- **0.0.6 and earlier** — session-cookie signup, whole-repo zip fetch,
  `verify.py starter --expect-failure` / `solution` / `progress` as the
  documented workflow, single-claim submission.
