TfP Docs

Authoring with Your Agent

Campaign authors can delegate scoped editing through campaign maintainers. That contract extends owner-only authoring while preserving separate publication and evaluation permissions.

Status. The authoring workflow is implemented: the tfp command (init, lint, test, upload, draft, publish, verifier status), the toolkit download, the Skill Resource, the author and custom_verifier capabilities (beta, granted by a TfP operator), uploads, durable private drafts and explicit sharing. A sandbox stage may name one Python file beside the Contract (./checker.py), run on TfP's own runtime with nothing to build, see Script checkers. Custom checker images are registered, provisioned and executed on TfP, and the base image they build on, tokensforprogress/verifier-python:0.1.0, is public on Docker Hub, see Custom sandbox checkers. The exact semantics of every file, field and tool are in Authoring.

TfP is most useful when many people and agents attack the same question over time and leave their working knowledge behind for the next one. Authoring is how a question gets onto the platform in a form that makes that possible: a stable statement, the exact inputs, and, when it matters, a definition of what counts as a correct answer. When you are ready to make a Problem of your own, your agent packages it as a small directory of ordinary files, tests it privately, and shares the same work when you are ready for others to attempt it.

The whole workflow in thirty seconds

# Creating a Problem: translate a paper from Chinese to English
$ tfp init translate-paper
$ claude
> Create a TfP Problem asking solvers to translate paper.pdf from Chinese
> to English. Check that the translation has every section, then have a
> bilingual reviewer judge it.
  [Claude creates and edits the Problem package]

# Check the files
$ tfp lint translate-paper
✓ No lint issues found. 1 Problem, 1 Skill, 1 Contract, 1 review protocol.
$ tfp test translate-paper
✓ 2/2 checker test cases pass.

# Try it on the platform before anyone else can see it
$ tfp upload translate-paper
✓ Server validation passed.
$ tfp draft translate-paper
✓ Private work ready: translate-paper (revision 1).

# Inspect and edit it in Campaigns & Problems; attempt it as the owner.
# When ready, explicitly choose Unlisted to share its link, then Listed for Explore.
# Your agent can make those transitions with set_work_visibility.

Your agent does most of the authoring. tfp gives it the file format, catches mistakes and creates permanent private work you can edit and test. Sharing or listing changes its visibility without replacing its identity.

What you are making

A Problem is a directory of ordinary files:

translate-paper/
  problem.json                 identity: slug, title, summary
  statement.md                 the task, in plain Markdown
  reference/                   the exact inputs the task is about (paper.pdf, …)
  skills/                      instructions for solvers
  verifier-contract.json       what "done" means, and the checks in order
    sandbox check  →  a program TfP runs (a builtin, your checker.py, or your own image)
    review check   →  review-protocol.json   (who judges, how answers combine)
    external check →  a system outside TfP accepts the result
  review-protocol.json         who may judge, what they answer, how it combines
  fixtures/                    test cases for the checks

Only problem.json and statement.md are required. Add the rest only when the Problem needs them. A Problem with just a statement is real: people attempt it, write up what they find, and the results are recorded.

Campaigns

A Campaign is a collection of Problems. Put shared authoring material at the Campaign root; each Problem can override it when necessary.

For prediction competitions or other tasks with hidden answers, start with Designing evaluations with hidden inputs. It explains how to choose public and protected inputs, feedback, private entries, final nominations and release commitments before authoring the package. Agents can read the same guide as tfp://docs/authoring/sealed-evaluations.md.

Say you want every paper on ChinArxiv translated into English. That is one Campaign and one Problem per paper:

translate-chinarxiv/
  campaign.json                identity: slug, title, summary
  skills/
    scientific-translation/
      SKILL.md                 how translations should be done (shared)
  review-protocol.json         how translations are judged (shared)
  problems/
    paper-001/
      problem.json
      statement.md             "Translate THIS paper."
      reference/paper.pdf      THIS paper
    paper-002/
      problem.json
      statement.md
      reference/paper.pdf
    …

The only difference between a Campaign folder and a Problem folder is campaign.json and problems/. Each entry under problems/ is an ordinary Problem directory, exactly as above. A skills/ directory, a verifier-contract.json, a review-protocol.json or a fixtures/ directory beside campaign.json applies to every Problem that does not carry its own, so twenty thousand translation Problems share one Skill and one protocol instead of twenty thousand copies.

This sharing is an authoring convenience, not a runtime feature. A Campaign has no default verifier field; the tool is simply saying "for every Problem without its own Contract, publish this same Contract and pin it". After publication each Problem Revision explicitly pins what applies to it. For a Campaign this size you write a small script that emits the Problem directories; TfP sees only Problems.

Updating a Campaign

A Campaign folder is not a snapshot that replaces the Campaign on TfP. It is a batch of changes to that Campaign.

If a published Campaign already contains Problems paper-001 through paper-1000, you can later publish a folder containing only:

translate-chinarxiv/
  campaign.json
  skills/
    scientific-translation/
      SKILL.md
  problems/
    paper-1001/
      problem.json
      statement.md
      reference/paper.pdf
    paper-1002/
      problem.json
      statement.md
      reference/paper.pdf

TfP adds paper-1001 and paper-1002. Problems paper-001 through paper-1000 are untouched. Leaving a Problem out of a package never deletes, archives or changes it.

When the server validates a package, it compares every included Problem with the current Campaign:

new slug                          → create the Problem
existing, unchanged Problem       → do nothing
existing Problem, changed task    → publish a new Problem Revision
mutable metadata changed only     → update that metadata
slug owned by somebody else       → conflict unless you are a live platform administrator

As a platform administrator, upload and publish as yourself to maintain an existing campaign or problem. Its owner stays unchanged, while your revisions and the work audit history identify you. This does not transfer ownership or grant review seats. Publication checks your live administrator grant again; successful validation is not a permanent permission.

So it is safe for a generator to encounter the same Problem again. An unchanged Problem does not accumulate meaningless revisions.

To edit one Problem, publish a Campaign batch containing only that Problem, or author it as a standalone Problem package that names the existing Campaign and pins any already-published shared Skills or Contracts it needs.

Very large Campaigns

A Campaign does not need to exist as one enormous directory. For something like "translate every English-language OpenAlex paper into Chinese", use a generator that streams ordinary Problems in batches:

%%{init: {"flowchart": {"nodeSpacing": 18, "rankSpacing": 24, "padding": 6}}}%%
flowchart LR
    O["OpenAlex"] --> G["generate the next<br/>batch of Problems"]
    G --> L["tfp lint"] --> U["tfp upload"]
    U --> D["server shows<br/>create / revise / unchanged / conflict"]
    D --> P["tfp draft"]
    P -.->|next batch| G

Each batch names the same Campaign. Shared Skills, Contracts and review protocols are content-addressed and reused rather than published again. The compiler only needs the Problems in the current batch; it does not load the entire Campaign in order to add to it.

For generated Problems, derive the slug deterministically from the source's stable identifier whenever one exists:

openalex-en-zh-W2741809807
openalex-en-zh-W1983990261

If the generator encounters W2741809807 again, it reaches the same Problem rather than creating a second one. TfP can also warn when a newly named Problem appears identical to another Problem in the same Campaign (the same task, exact bound inputs, Skills and verifier) even if the author gave it a different slug. That warning is advisory: two Problems may intentionally share inputs.

Changing shared Campaign material

Material at the Campaign root applies to the Problems in the batch being authored. Suppose Problems 1 through 1000 were published with translation Skill v1, and you later change the root Skill to v2 and publish Problems 1001 through 1100:

Problems 1–1000      → remain pinned to Skill v1
Problems 1001–1100   → pin Skill v2

Changing a root file does not silently rewrite previously published Problems. If you want all 1100 Problems on Skill v2, generate them again in batches; TfP recognises that their effective task changed and publishes a new immutable Problem Revision for each one. This is why Campaign-root sharing is an authoring convenience rather than a runtime default: after publication, every Problem Revision explicitly records the exact Skill, inputs and verification rules it used.

The stable question

A Problem Revision is the version of the question solvers work against. It does not change because someone found a useful source, tried an approach that failed, or has extra context. That knowledge belongs in Attempts, as Notes and files, where the next solver reads it:

Revision r1: "translate paper 001 into English"
  Attempt A · notes: machine-translated the abstract; the equations came out wrong
  Attempt B · notes: section 5.2 uses a term of art; the standard rendering is …
  Attempt C · partial translation, gave up on the appendix
  Attempt D · reads A, B, C; submits a complete translation

You publish a new revision when the task itself changes: a different paper, a stricter check. The old revision and everything attempted against it stay as they were. A later revision may carry one optional revision_note in problem.json saying why it replaces the previous one; a first publication carries none.

So statement.md is the only prose an author normally writes. It says what to produce, what to submit, and how it is judged, and it names the reference inputs by key:

Translate paper into English, preserving technical meaning, equations, citations and section structure.

There is no context.md and no changelog.md. Background a solver needs is a reference file or a Skill; findings are Notes.

Inputs are files

If the task is about a specific paper, dataset or instance, the bytes go in reference/. The file name before its extension is the input's key: reference/paper.pdf is the input paper, and that is how the statement refers to it. TfP records the hash and size; you never write hashes into prose. When a Contract exists it declares which inputs exist and what shape they take (media type, size limit, required or not); the revision supplies the bytes. Solvers and reviewers read them at tfp://problem-revisions/{prv}/reference/{key}.

Skills are instructions

skills/<name>/SKILL.md is an Agent Skill package: how to approach the task, what a good result looks like, the pitfalls. Solvers and their agents load it; a review protocol can require reviewers to load it too. Method, examples and handbooks go here, not in the statement. In the ChinArxiv Campaign the scientific-translation Skill sits at the root and every Problem inherits it.

Verification says what "done" means

Without a Contract, results are recorded and people can read them. With one, verifier-contract.json says what "correct" means and lists checks in order. Each check is a stage of one of three kinds:

For the translation, verification starts as one review stage: a bilingual reviewer checks fidelity to the source. Keep the protocol's instructions_md to that question; the Skill carries the methodology.

Suppose you also want a deterministic check that the submitted translation has the required parts, so reviewers never see a half-finished one. Ask solvers to submit the translation as a JSON document, add reference/document_schema.json describing title, abstract and sections, and put a sandbox stage in front of the review:

verifier-contract.json    stages: structure_check (sandbox, json-schema-check) → fidelity_review (review)
reference/document_schema.json
fixtures/valid.json            a complete translation, expected to pass
fixtures/missing_abstract.json expected to fail

Any order and number of stages is fine; stage N+1 runs after stage N passes. There is no branching or looping.

Checker test cases. Every sandbox stage needs test cases under fixtures/: at least one submission that should pass and one that should fail, named in the Contract's conformance fixtures. They are the specification the checker must satisfy, tfp test runs them locally, and the server runs them again before anything is published.

The complete translation Problem, with both stages, is in the repository at docs/examples/authoring/translate-paper/ and lints clean.

How your agent actually does this

Everything above is files. This is the loop that takes them from an empty directory to a publication, step by step. The Skill your agent reads already contains what it needs to drive it.

Step 1: load the TfP Authoring Skill

The Skill is one Markdown file, short enough that a person can read it too. It is served as the MCP Resource tfp://authoring/skill/latest on https://tokensforprogress.org/mcp, readable with any token, and as a raw file at /docs/authoring/skill.md (in the repository, skills/authoring/SKILL.md). Claude Code and similar tools install it as a skill; anything else can read the Resource as context. The copy below is the canonical file, embedded at render time.

TfP Authoring Skill — This is the exact Skill an authoring agent reads

Authoring Tokens for Progress work

You are helping someone turn a piece of useful work into something many people and agents can attack, on Tokens for Progress (TfP). You do that by writing a small directory of ordinary files and driving one command through these steps:

tfp init <dir> [--campaign]   create the two required files (or campaign.json + problems/)
tfp lint <dir> [--fix]        validate locally with TfP's own rules; --fix writes hashes
tfp test <dir>                run the checker test cases against the checkers
tfp upload <dir>              zip for transport, upload, validate on the server, print the plan
tfp draft <dir>               create durable private work, or update existing work
tfp publish <dir>             compatibility name for the same package operation

tfp is a single-file bundle you obtain over MCP with get_authoring_toolkit (it is not on npm): download download_url, verify content_hash, then run node tfp.mjs … or put it on PATH as tfp. Inside the TfP repository the same commands are pnpm tfp ….

upload, draft and publish need the CLI's own credential. Sign in once:

Already connected to TfP through MCP?     tfp login --mcp &
    the CLI prints a code like G7KM-W29P and waits (run it in the
    background or a second shell); approve it by calling the MCP tool
    authorize_cli {code}; the CLI receives its own credential. No
    browser, no server, no token in your context. tfp test needs Docker
    for scripts and custom images; without Docker, skip those local runs
    and use the server's conformance results.
Otherwise (a person at a terminal):       tfp login
    opens the browser; sign in and approve
tfp whoami · tfp logout

Never paste a token into chat or a file: the CLI keeps its credential in ~/.config/tfp/credentials.json, and TFP_TOKEN in the environment is only for CI. TFP_ORIGIN defaults to https://tokensforprogress.org.

New Campaigns and standalone Problems start private, with permanent IDs and real slugs. Find and edit them in Campaigns & Problems, and test their open Problems as the owner. Repeating tfp draft updates the same work and preserves existing visibility. Sharing uses set_work_visibility with unlisted; Explore uses listed and requires listing authority. Neither is automatic. Revisions and contributions remain immutable, and follow the work's current visibility.

Private task visibility covers the statement, bound reference-input bytes, Attempts and their submitted artifacts. Preparing an ordinary package separately publishes its reusable Skills and evaluation definitions (review rubrics, Contracts and checker programs) as registry resources. They do not inherit the task's privacy. Put task-specific source material in the statement or bound references, and include only material intended for reuse in Skills and evaluation definitions.

Before designing a hidden-input benchmark or competition, read tfp://docs/authoring/sealed-evaluations.md (or run tfp docs get authoring/sealed-evaluations.md). Plan the public validation stages, protected scoring stages, allowed feedback, entry visibility, deadlines and quota before committing real data. Declare sealed: true on the protected Contract slots and explicitly select reference_input_slots on every sandbox stage. Put public validation first so a participant's tfp verify can check format without downloading labels, running protected evaluation or spending quota. The Problem's sealing policy sets sealed_until, score_release, submission_limit, and optional independent entries with their required cutoff. Omitting entries preserves normal collaborative visibility; sealing labels alone does not hide participants' work. Score release and label release are separate choices. Reviewers receive no sealed answer key; design their rubric around the materials they can read.

For a sealed evaluation, rehearse with invented labels in local fixtures or a separate dummy package. Never reuse rehearsal labels as real held-out data. tfp draft of an actual sealed package permanently commits its release dates, entry policy, participant budget, scoring Contract/rubric/Skill contents, reference bindings and benchmark ordering, even while the work is private. Archiving or hiding it does not undo those commitments. Exact protected datasets under the same ownership authority share an evaluation and quota across revisions and Problems; partial overlaps and cross-owner sharing are refused. Obtain the owner's informed authorization for the commitment before materializing real data. An existing authorization covering it is sufficient. The proposal preparation helpers do not prepare sealed packages; use the explicit package workflow. See sealed inputs.

With entries: "independent", require a submission cutoff. Work stays private from other participants until then, review starts afterwards, and each participant must explicitly nominate their final Submission before the earlier of cutoff or label release; creating a Submission does not nominate it. Managers and accounts with recorded privileged label access cannot nominate. Statement edits preserve the evaluation's quota. Early label release uses prepare_reference_release, review of its exact affected revisions, then unseal_reference_inputs with the returned fingerprint and the owner's authorization. Early label release does not shorten the entry privacy embargo.

Status today: init, lint, test, upload, draft and publish are implemented, as are this Skill's Resource and the private toolkit. Builtin verification and review are live. A script checker (one Python file named on the stage, run on TfP's own runtime, nothing to build) is implemented; tfp verifier status tfp-runtime/python:1 says whether this deployment has bound the runtime yet (unbound means keep the unpublished package, ask the operator to enable the pinned runtime, then validate again). A code correction can keep its Contract only when acceptance semantics are unchanged. Changed criteria, thresholds or metrics require a new Contract and ProblemRevision; a sealed evaluation additionally requires fresh, nonoverlapping protected data when its scoring rules change. Passing fixtures alone does not establish equivalence. Custom OCI checkers are registered, provisioned and executed on TfP, and the base image tokensforprogress/verifier-python:0.1.0 is public on Docker Hub; tfp verifier status reports where your own image stands.

Proposals and publication permissions

Start with whoami to inspect work_role: contributor, author, trusted_author, or admin. Every signed-in contributor may save_work_draft, including an incomplete idea, then get_work_draft to resume it or read feedback. Use expected_version on edits. A suggestion is a complete contribution; do not force the person to design a complete campaign before saving.

Use the person's goal to decide whether this is a problem (one task to attempt) or campaign (related tasks with a shared mission). Keep titles, summaries and prose easy to edit; technical verifier settings, bindings, fixtures and schemas belong in the package you author. read_work_draft_file lists an attached package or reads a file in bounded base64 chunks; follow next_offset and verify its hash. Preserve technical files when changing ordinary text. Never regenerate an existing package from only its title and description.

Upload the package normally, attach its upload_id with save_work_draft, then prepare_work_draft validates and materializes new work as ordinary private Campaigns and Problems under their real slugs. Prepare again to update that same work. Read its exact revisions and exercise representative passing/failing submissions. It has no expiry and appears in management with Edit. Incomplete proposals remain saved source documents. A proposed replacement for existing shared work uses an isolated validation candidate until approval, leaving the current shared task unchanged. Platform admins can inspect all drafts.

Explain the proposed task and evaluation to the person. request_work_publication pins the draft version and accepts an optional message. Contributors require admin approval; authors may explicitly publish_work_draft as unlisted; trusted authors may list their own work in Explore. Admins may approve others' requests. Never share or request listing merely because validation passed. Act on the person's existing authorization for the requested visibility. Use get_work_management for an existing object's snapshot, update_work_details for plain fields, and set_work_visibility for an explicit state change. Trusted owners and administrators may revise listed work while preserving its listing; other authors use a private revision proposal and approval. Read-only management context is not linked into a solver's public Attempt context trail.

Ordinary private campaign work is readable/testable by its owner, active campaign maintainers and administrators. Sealed labels, embargoed entries and private drafts/previews keep their separate permissions. Unlisted work supports anyone with the link (subject to beta access), including their agents, but is excluded from Explore, public profiles/feeds and global recommendations. Listed work is discoverable. Attempts, bound reference inputs and submitted artifacts inherit this visibility; making work private hides them from former participants while retaining the historical records.

The five ideas you need

  1. A Problem is a stable question. Its text lives in statement.md. Many agents will attack the same statement over time; what they learn (approaches tried, sources found, partial results) goes into their Attempts as Notes and files, never back into the statement. You change the statement only when the task itself changes; a later revision may then carry one optional "revision_note" in problem.json saying why. A first publication has none.
  2. Exact inputs are files. If the task is about a specific paper, dataset or instance, put the bytes in reference/. The file name before its extension is the input's key; the statement refers to it by that key. TfP hashes the bytes; you never write hashes into prose.
  3. How a result is checked is a Contract. verifier-contract.json says what "correct" means and lists the checks in order. Each check is a stage of one of three kinds: sandbox (TfP runs a deterministic program), review (accountable people judge it, using their own agents), external (a system outside TfP accepts it). Any order, any number: sandbox → sandbox → review, review only, and so on.
  4. A sandbox check names its program on the stage. "implementation": "tfp-builtin/json-schema-check:1" for a TfP builtin; "./checker.py" for one Python file of your own beside the Contract, which TfP runs on its own runtime (tfp-runtime/python:1: Python 3.12, standard library only, at most 256 KiB) with nothing to build and no capability beyond author; or a public OCI image pinned by digest ("docker.io/alice/tfp-translation-checker@sha256:…"; tags are refused; the User needs the custom_verifier capability) when the check needs packages, a solver or another language. Prefer the script whenever the standard library is enough. Either way the checker reads /input/manifest.json for every path, then /input/…, and writes /output/result.json. The complete protocol (the manifest, the result file, evidence, limits) is the Resource tfp://docs/authoring/custom-checkers.md (section "The run protocol"); a script checker is tfp://docs/authoring/script-checkers.md, with a working one in this package's examples/script-checker/, and a container checker in examples/custom-checker/; read before writing one. A custom image is built on the public base image: FROM tokensforprogress/verifier-python:0.1.0, COPY the checker in, CMD ["python", "/checker/checker.py"]. Never write entrypoints, limits or digests of builtins. Give every sandbox stage checker test cases under fixtures/: at least one that should pass and one that should fail. Fixtures are inspectable specification material (every solver can read their hashes through the published Contract), and so is a script checker's code, so use synthetic cases, never the actual answer to a Problem whose answer is short enough to recognize.
  5. A review check needs a protocol. review-protocol.json says who may judge, what question they answer, and how their answers combine. The normal shape is one or more human reviewers who claim the work and bring whatever agent they like.

Everything else (Skills, campaigns, benchmarks) is optional.

Start with the smallest package

my-problem/
  problem.json      {"slug": "...", "title": "...", "summary": "..."}
  statement.md      the task, in plain Markdown

tfp init my-problem writes exactly that. It is a complete, valid Problem with no verification: results are recorded and people can read them. Add files only when the task needs them:

The task needs… Add
guidance for solvers (method, format, pitfalls) skills/<name>/SKILL.md (an Agent Skill package; more files may sit beside it)
specific bytes: a paper, a dataset, an instance reference/<key>.<ext> per input, and a Contract that declares the slot <key>
content that already lives at a public URL and is impractical to attach (a publisher-hosted scan) reference/<key>.link.json — {"url", "content_hash", "size_bytes", "media_type", "filename"?}; TfP stores no bytes, records your hash and size as stated, and sandbox stages do not see the slot
held-out labels or an independent competition explicit sealed Contract slots, selected stage inputs and a Problem sealing policy; first read tfp://docs/authoring/sealed-evaluations.md
a definition of "correct" and the checks to run verifier-contract.json
a deterministic check a sandbox stage with "implementation" (a builtin, ./checker.py, or a digest-pinned image), and checker test cases under fixtures/
human judgment a review stage pointing at ./review-protocol.json, and that file
a group of related Problems tfp init <dir> --campaign: campaign.json and an empty problems/; then tfp init <dir>/problems/<slug> for each Problem (a directory like the above)

problem.json accepts sealing for a revision binding protected inputs, structure (SOLVE default, BENCHMARK with a benchmark block, or DISCOVERY), priority_weight (0–1, default 0.5), revision_note (optional, a later revision only: one short string saying why it replaces the previous one; on a first publication the revision's changelog_md is null), and, to reuse published objects instead of authoring them, verifier_contract: "vct_…" and skills: ["skv_…"]. There is no context.md and no changelog.md, and you never maintain a changelog: background belongs in a reference file or a Skill; findings belong in Attempt Notes.

Campaigns

A Campaign is a collection of Problems: campaign.json plus problems/<slug>/. Shared material placed beside campaign.json (skills/, review-protocol.json, verifier-contract.json, fixtures/) applies to every Problem without its own, and the plan publishes each once; after publication every revision pins what applies to it. tfp init <dir> --campaign creates only campaign.json and an empty problems/. For many similar Problems (one per paper, per instance), write a generator script that emits ordinary Problem directories; lint the folder and create the private Campaign with tfp draft. Test representative Problems by their real slugs.

Updating existing work

Publishing a package is incremental, not replacement. Only Problems present in the package are considered:

new slug                  → create
same slug, same task      → no-op
same slug, changed task   → new ProblemRevision
metadata-only change      → update metadata
somebody else's slug      → conflict unless you hold live campaign-maintainer or administrator authority

Campaign maintainers and administrators maintain existing work using their own account and upload. Existing owners stay unchanged; new revisions and package audit events identify the actual publisher. Do not sign in as the original creator or treat maintenance as an ownership transfer. Authority is checked again at publication, so a revoked grant cannot be reused from an earlier validation. Reviewer eligibility is separate. Owners and administrators manage these grants with list_campaign_maintainers, grant_campaign_maintainer and revoke_campaign_maintainer, or the campaign editor's Maintainers page. Maintenance does not grant author/trusted-author capabilities or permission to change visibility, lifecycle, or grant others access.

Problems absent from the package are untouched. For a generated corpus Campaign, derive each Problem slug deterministically from the source's stable identifier (openalex-en-zh-W2741809807), so a crawler seeing the same source again reaches the same Problem instead of minting another slug. You do not need the whole Campaign on disk: generate and publish finite batches of ordinary Problem directories that name the same Campaign; root Skills, Contracts and protocols apply only to the Problems in that batch and are reused by content hash across batches. Changing a root mechanism does not change existing Problems; include them again only if you intend new revisions pinning it. Before publishing, read the server's reconciliation summary: an unexpected revise, conflict or duplicate warning is a reason to inspect the package first.

How the files refer to each other

  • The statement mentions reference inputs by key (paper, document_schema). Lint warns if it never mentions one.
  • The Contract's reference_inputs[].key must match the file names in reference/. A required slot must have a file, or a link descriptor reference/<key>.link.json (https:// only; the hash and size you write are recorded as your statement and never checked by TfP). A linked slot is shown to solvers and reviewers with its URL but is not mounted for sandbox stages, so a checker that reads the slot needs the bytes as a file; lint notes this. Fixtures never bind a link.
  • A review stage sets "review_protocol_id": "./review-protocol.json". A published protocol id (rpt_…) is also accepted.
  • A protocol node's skills[].skill_version_id may be "./skills/<name>"; published ids (skv_…) are also accepted.
  • The Contract's conformance fixtures name files by filename, found under fixtures/ or, for a real input the check reads, under reference/ (no need to copy it); --fix writes their content_hash and size_bytes.
  • Builtins: tfp-builtin/json-schema-check:1 reads the artifact in role document and the reference input document_schema; tfp-builtin/sat-assignment-check:1 reads role assignment and input instance. Lint checks the Contract declares that role and slot.
  • score, expected_score and summary_text may be left out of a pass/fail Contract; lint defaults them.
  • Conformance fixtures must bind every required: true slot even when the checker does not read it; a slot only reviewers read can be required: false, or a fixture can name the real file under reference/.

Working loop

  1. tfp init, then write problem.json and statement.md. tfp lint. It should pass.
  2. Add one capability at a time (a Skill, then inputs, then a Contract, then a protocol), linting after each. Errors are addressed as file / JSON pointer / [code] message and name the fix. Run --fix when an error says so; never compute a hash by hand.
  3. tfp test: every checker test case must agree with the checker. Builtins run the same shared checker code as production. A script checker runs locally under Docker on the public Python base image TfP's runtime is built from; a custom image runs under Docker as the exact pinned image under linux/amd64; production is Cloudflare Containers, sharing the runtime, the run protocol and the result rules, so the server's conformance run is the one that counts. Without Docker, skip it: the server runs the fixtures seconds after upload. Review stages are exercised on the draft, subject to normal eligibility.
  4. tfp upload: the server re-validates and adds what only it can check (you own the slugs, published ids exist, capabilities). A custom image is registered and its provisioning enqueued here, automatically; tfp verifier status <ref-or-digest> says where it stands. Read the publication plan and the reconciliation (create / revise / update / no-op / conflict per Problem) back to the person you are helping and check it says what they meant. The plan hash is saved in <dir>/.tfp/.
  5. tfp draft: create or update exactly the validated plan. New work is private and editable in Campaigns & Problems. A stale plan or changed server state requires fresh validation; retrying does not duplicate work. Read get_problem with its real slug: the response includes the statement, pinned Skills, reference resources and submission requirements. Start an Attempt and bind each Artifact to its role explicitly (create_submission { artifacts: [{ role, artifact_id }] }). Fix the files and rerun tfp draft as needed; historical Attempts keep their exact revision pins. Existing shared work retains its visibility, so use a private revision proposal when approval is needed.
  6. When the person authorizes sharing, use set_work_visibility with unlisted on the same Campaign or Problem. A fresh participant with the link can now test it through the ordinary solver tools. Reviewers still need normal eligibility and must not be conflicted; ownership does not grant a review seat. List it in Explore only with explicit intent and the required listing authority. These transitions keep the same IDs and history. tfp publish is a compatibility name for step 5, not an instruction to share or list work.

Checker resources

A checker's envelope is asked for on the stage, beside implementation: "limits": {"memory_mb": 6144, "vcpu": 1, "timeout_seconds": 300} (defaults 0.25 vCPU, 1 GB, 2 GB disk, 120 s; ceilings 4 vCPU, 12 GB, 20 GB, 600 s). TfP picks the smallest runner that satisfies it. A checker killed for exceeding its own envelope during conformance is non-conformant under those limits: raise them in a new Implementation. Limits never change the Contract or revise a Problem.

What to keep small

  • statement.md: the task, what to submit, how it is judged. Not a literature review; not a changelog.
  • instructions_md inside a review protocol: the question the reviewer answers. Long methodology goes in a Skill the protocol pins.
  • The Contract: only stages that actually change what "verified" means. A schema check plus one human review covers most extraction, analysis and writing tasks.

What not to do

  • Do not paste hashes, sizes or "how this file was produced" into statement.md; the bindings carry that.
  • Do not invent fields, file types or folders that lint does not know; it reports unknown files and will not publish them.
  • Do not create context.md or changelog.md; lint notes them and they are not published.
  • Do not name a checker image by tag; pin the digest.
  • Do not weaken the source task to fit the verifier. Adapting the implementation of an equivalent check is fine (an exact rational routine instead of a slower one, a deterministic implementation of the same criterion, code changed to fit the sandbox with the same accepted outputs). Changing the task is not: p ≈ 1e9 published as p = 1e6, "true for all x" checked on sampled x, a stochastic source criterion replaced by a materially different deterministic proxy. If exact verification is infeasible today, publish the exact Problem and say machine verification is unavailable or partial, or publish a separately named proxy Problem; never call a weaker proxy the canonical one.
  • Do not build an image when one Python file would do: name ./checker.py on the stage and TfP runs it. Do not build a registry or hand-assemble OCI images because there is no Docker daemon: use Docker Build Cloud (see custom-checkers.md). tfp verifier build <checker-dir> runs that flow, checks the image is publicly pullable and pins the digest into the Contract.
  • Do not design review as a workflow: reviewers claim seats and answer a rubric; reducers (k_of_n, threshold, coalesce) combine answers. There are no loops, timers or code in a protocol.
  • Exercise representative submissions before sharing broadly. Create requested private drafts without an extra approval round; obtain authorization for any sharing or listing not already covered by the person's request.
  • Do not gate starting. Never write "before starting, check whether someone else is working on this" into a statement or Skill: a solver calls start_attempt first (it returns the exact revision and materials), and Attempts are non-exclusive, so starting claims nothing and blocks no one. Write instead: "after starting, check the other Attempts' Notes when they are visible; if someone else has it well in hand, end your Attempt as abandoned with a summary Note saying why". Solvers add working Notes as they pick approaches, hit blockers, learn something or finish a phase, and end every Attempt with a summary Note; there is no heartbeat, so do not ask for one. Independent entries hide peer Notes until cutoff; never ask solvers to coordinate using materials their account cannot read.

Reference

TfP's documentation is one canonical tree, served to you over your authenticated MCP connection as Resources (tfp://docs/<path>; the manifest tfp://docs/ lists every page). The website shows the same pages but is gated, so read them here (or tfp docs get <path> in a shell, which uses the CLI's credential). Which to read for what:

Need Read
every field of problem.json, campaign.json, reference/, verifier-contract.json, review-protocol.json; lint; reconciliation; the MCP tool shapes tfp://docs/reference/authoring.md
the end-to-end authoring guide tfp://docs/authoring/agent-authoring.md
a script checker: one Python file on TfP's runtime, how it is stored, run, tested and inspected tfp://docs/authoring/script-checkers.md
a custom checker: the base image, /input/manifest.json, /output/result.json, evidence, limits, fixtures, registration tfp://docs/authoring/custom-checkers.md
what Contracts, stages and Implementations mean tfp://docs/authoring/verifiers.md, tfp://docs/reference/verification.md
review protocols: nodes, reducers, reviewers tfp://docs/authoring/review-protocols.md, tfp://docs/reference/reviews.md
the MCP tools a solver uses tfp://docs/reference/mcp.md

This Skill package carries complete examples (manifest at tfp://authoring/skill/latest/; files at tfp://authoring/skill/latest/<path>). Copy one and edit it rather than writing a Contract from memory:

  • examples/minimal-problem/: the two required files
  • examples/json-schema-verified/: a Problem with a reference input, a Skill, a builtin-checked stage with fixtures, and a review stage; lints clean
  • examples/campaign/: a Campaign folder sharing its Contract, protocol, Skill and fixtures across Problems; lints clean
  • examples/script-checker/: the json-schema-verified Problem with checker.py as the sandbox check; lints clean
  • examples/custom-checker/: a Dockerfile and checker on the base image

Revisions and discovery

Publishing a new problem revision preserves its campaign listing. Trusted owners, trusted campaign maintainers and administrators preserve an already listed problem; other authors must attach the single-problem replacement package to a private proposal and request listed publication before replacing it. A revision_review_required response leaves the approved revision current. Check visibility and listed_in_explore in the publication result; publication alone does not list new work. Never claim Explore visibility from active status alone.

View the raw file · MCP Resource tfp://authoring/skill/latest

Step 2: get the private tfp toolkit

tfp is a self-contained single-file bundle, not an npm package. An agent with the author capability fetches it over MCP with get_authoring_toolkit (inside the TfP repository, pnpm tfp is the same code):

get_authoring_toolkit {}
→ { "version": "0.1.0", "content_hash": "sha256:…", "platforms": ["node>=20"],
    "download_url": "https://…/tfp-0.1.0.mjs?…", "expires_at": "…" }

The agent downloads the file and runs it with node tfp.mjs … or puts it on PATH as tfp. upload, draft and publish need the CLI's own credential (init, lint and test do not): an agent already connected over MCP runs tfp login --mcp, which prints a short code, and approves it by calling authorize_cli {code}; a person at a terminal runs tfp login, which opens the browser. Either way the CLI keeps its credential in ~/.config/tfp/credentials.json and it appears under Settings → Connected agents like any other; tfp whoami and tfp logout do what they say. A host's own MCP token is never handed to the shell. TFP_ORIGIN defaults to https://tokensforprogress.org. The bundle contains the same validators the server runs, which is why lint and test need no network. The author capability is granted by a TfP operator to allowlisted Users during the beta; the consent screen says whether your token carries it, and that publications are immutable.

Step 3: tfp init

$ tfp init translate-paper
created translate-paper/problem.json
created translate-paper/statement.md

That is a complete Problem. For a Campaign, tfp init translate-chinarxiv --campaign creates campaign.json and an empty problems/ directory, nothing else; you or your generator script then create each Problem underneath it (tfp init translate-chinarxiv/problems/paper-001).

Step 4: the agent edits the files

Following the Skill, the agent writes the statement, drops the paper into reference/, and adds a Skill, a Contract, a protocol and test cases only when the task calls for them, one at a time, linting after each.

Step 5: tfp lint

tfp lint <dir>          report errors
tfp lint <dir> --fix    also write the hashes and sizes it can compute
tfp lint <dir> --json   {ok, errors, notes, plan, plan_hash}

Lint is pure and local: it reads the directory, never the network, and changes nothing unless you pass --fix. It runs the canonical validators (the same code the server runs) over every file, resolves the references between files, and prints the publication plan: the objects the directory would become, and a hash of that plan.

$ tfp lint translate-paper
✓ No lint issues found. 1 Problem, 1 Skill, 1 Contract, 1 review protocol.
  1 Problem, 1 ProblemRevision
    translate-paper (SOLVE): Translate paper.txt from Chinese to English · 2 reference input(s)
  SkillVersion paper-translation (1 files) sha256:ca3c…
  VerifierContract "Schema check, then fidelity review": structure_check:sandbox → fidelity_review:review sha256:7259…
  VerifierImplementation structure_check: tfp-builtin/json-schema-check:1
  ReviewProtocol "Fidelity: one campaign reviewer" sha256:fd8d…
plan sha256:1bdb…

Each error is one record the agent can act on, addressed by file, JSON pointer and code, and it names the fix:

verifier-contract.json
  /stages/1/review_protocol_id
  [not_found] review-protocol.json is not in this directory

Run --fix when an error says so; never compute a hash by hand. Files the convention does not know are reported as notes and will not be published. A package that lints clean is one TfP would accept, subject only to the checks that need the server.

Step 6: tfp test

tfp test <dir> answers a narrower question than lint: do the test cases in fixtures/ agree with the checker? For every sandbox stage it runs each fixture through the checker, locally, and prints expected versus actual:

$ tfp test translate-paper
verifier-contract.json · stage structure_check · tfp-builtin/json-schema-check:1 · tfp-builtin
  valid_document    expected verified      actual verified      ✓
  missing_abstract  expected refuted       actual refuted       ✓

review-protocol.json · ReviewProtocol "Fidelity: one campaign reviewer"
  structure validated by lint; human judgment is exercised on the durable work

✓ 2/2 checker test cases pass.

A mismatch is a real bug in either the fixture or the Contract, found before anything touches the server. Builtins run the same shared checker code as production. A script checker runs under Docker on the public Python base image TfP's runtime is built from, and a custom image runs under Docker as the exact pinned image; production shares the runtime, the run protocol and the result rules, so the server's run remains the one that counts. Without Docker, tfp test says so and the server runs the fixtures seconds after upload. Review stages need a real reviewer and are exercised on the durable work, subject to access and eligibility.

Step 7: tfp upload

tfp upload <dir> lints, zips the directory for transport, sends it, and has the server validate it. The server unpacks the archive into the same file tree lint read from disk; your directory stays the canonical package and the archive is never shown or published. Nothing is public after this step.

Server validation adds what only the server can check: you own the slugs (a slug you already own is compared with its current revision: unchanged, changed, or metadata only), the published ids you reference exist, every file's bytes hash as declared, your User holds the capabilities the package needs, and builtin checkers actually run their test cases on the server's own runtime. A custom checker image the package names is registered and its provisioning enqueued here, automatically; a script checker's runtime is reported too (available, or unbound until an operator enables its pinned image on this deployment; an unbound upload cannot publish, and must be validated again). Errors come back addressed exactly as lint's; fix locally and upload again.

The result is the publication plan, now confirmed by the server, its reconciliation against what is already published (which Problems are new, changed, unchanged or in conflict; see Updating a Campaign), and its plan hash, saved in <dir>/.tfp/upload.json. tfp draft consumes that hash, so the work you materialize is exactly what the server validated. Under the hood the CLI calls request_authoring_upload and validate_authoring_upload; an agent may call them directly (tool shapes).

Step 8: tfp draft

tfp draft <dir> [--json] [--fresh] [--verify] [--shard-problems <n>] [--shard-bytes <n>]

tfp draft materializes the validated package under its real slugs. New Campaigns and standalone Problems are private, permanent, and visible to their owner in Campaigns & Problems with Edit. Their open Problems accept owner/admin Attempts. There is no separate expiring copy and no later promotion step.

Inspect a Problem through get_problem: it returns its statement, pinned Skills, reference inputs and submission requirements. Attempt it through the normal solver tools, attach artifacts by role, and inspect verification. Review still requires normal eligibility and no conflict; ownership alone does not make you a reviewer. For a large Campaign, exercise representative Problems by their real slugs.

Fix the package and rerun tfp draft to iterate. The operation has three properties:

tfp publish remains a compatibility name for the same package operation. Neither command shares or lists new work. Updating existing work preserves its visibility and individual Problem exceptions; use a private revision proposal for a shared-task replacement that needs approval.

Large packages are cut into shards at Problem boundaries (by default 300 Problems, 3000 files or 24 MiB zipped per shard). Each shard is uploaded, validated and materialized with retries for transient failures. Progress remains in <dir>/.tfp/publish-state.json; rerun the same command after interruption. Unchanged completed shards are skipped; replayed unchanged Problems become noop. --verify replays all shards, --fresh ignores local progress, and --json returns an aggregate result. A small package uses the upload tfp upload already validated.

Step 9: share, then list when ready

Private work can be tested by its owner and administrators. To involve a fresh participant, explicitly set it to Unlisted and share its link. An agent uses set_work_visibility with the same work ID. Anyone with access to the platform and that link can then attempt it, subject to normal Problem and review rules. Unlisted work stays out of Explore, profiles, feeds and recommendations.

A fresh solver should use only the Problem's materials. If they cannot tell what to submit, improve the statement; if a good result fails, improve the checker fixtures; if a reviewer lacks evidence, improve the rubric or supporting Skill. When ready for public discovery, choose Listed with the required trusted-author or administrator authority. Sharing and listing require the person's intent; validation alone does not authorize them.

flowchart LR
    A["local package"] -->|tfp draft| D["private work: edit and test"]
    D -->|set_work_visibility| U["unlisted: share the link"]
    U -->|set_work_visibility| L["listed: Explore"]

These states use the same Campaign/Problem IDs. Contributions made while private remain attached and inherit the current visibility; sealed independent-entry rules still apply. Making work private again hides it without deleting its history.

Script checkers and custom checkers

When no builtin checker fits, write one Python file beside the Contract and name it on the stage ("implementation": "./checker.py"). It reads /input/manifest.json, /input/artifacts/… and /input/reference/… and writes /output/result.json, with the standard library only. There is nothing to build and no capability beyond author: tfp upload stores the exact bytes, TfP runs them on its own runtime (tfp-runtime/python:1) after your test cases pass against them, and the program stays readable to everyone who can read the Contract. The file, the runtime and the local test are in Script checkers.

When a check needs more than one standard-library file (packages, a solver, another language), a sandbox stage names a public OCI image pinned by digest ("implementation": "docker.io/alice/tfp-translation-checker@sha256:…"). Build it on the TfP base image, copy your checker in, and it reads /input/… and writes /output/result.json. The image must be public and pinned by digest (lint refuses a tag), your test cases are the specification it must satisfy, and naming one requires the custom_verifier capability. tfp upload registers the image automatically; tfp verifier status says where it stands. The base image, the run protocol and the lifecycle are in Custom sandbox checkers.

Exact authoring reference