---
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": ""}'
```
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": ""}'
```
- `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/` â 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//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
```
This learner check runs the tests against `starter/` only. It prints
per-checkpoint status and exits non-zero while a test gate is open or a
self-reported checkpoint is pending. `[open]` means keep working in
`starter/`; `[passed]` means the local suite passed. `[pending]` requires
the human to answer that lesson's checkpoint questions. Once they have,
run `python verify.py --attest l01` (repeat `--attest ID` for each completed
self-reported checkpoint) to print its `[attested]` code. Never mark all
self-reported checkpoints just because the tests pass. The reference
`solution/` is checked by maintainers when publishing the course.
For a versioned machine result use `python verify.py check --json` (v2).
Its exit status is strict: 0 only when every checkpoint is passed or
explicitly attested. The legacy `progress --json` output remains available
for v1 integrations but prints self-reported codes without confirmation;
ignore those rows when submitting claims.
With `FLYPYTHON_CLAIM_SECRET` exported, `python verify.py progress --json`
produces v1 signed local-run receipts for the gated checkpoints. Match
those receipt objects to codes earned by the learner check; do not submit
the legacy `progress` self-reported rows. `progress --receipt-out
receipts.json` writes the same optional receipts to a file.
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` | the learner answers the lesson checkpoint questions | the code printed only after explicit `--attest ID` |
| `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": "", "checkpoint": "l01", "code": ""},
{"course": "", "checkpoint": "l03", "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.