Using thinkingface from AI agents¶
An AI coding agent — Claude Code, or any client that speaks the Model Context Protocol — can run the experiment loop you would otherwise run by hand: launch a training job, wait for it, read the metrics, compare it with earlier runs, and write down what it learned. thinkingface gives an agent three ways in, all backed by the same API and the same access tokens:
| Surface | Best for |
|---|---|
The MCP server, tf mcp |
Agents with MCP support. The experiment tools appear as native tools, with their arguments and limits described to the model. |
The tf CLI with --json |
Agents that work through a shell, and scripts. Every command prints machine-readable JSON on stdout. |
The HTTP API, described by /api/openapi.json |
Anything else — your own tooling, other languages. |
This page covers each of them, a typical loop, and how to keep an agent's token to the minimum it needs. The runs themselves are logged by your training script as usual — see Tracking Experiments.
Give the agent its own token¶
Hand an agent a token of its own rather than yours: a write token restricted to the experiment repository it works on, with an expiry. It can then log runs, annotate them, set metric goals and edit the project's notes in that one repository — and nothing else: it cannot push to another repository, delete or rename anything, or mint itself a new token. Reads are not narrowed by the restriction, so the agent can still read every repository it could read anyway.
Create it at Settings → Access tokens: scope write, an expiration of 7 or 30 days, and the
repository under Restrict to repositories (for example datasets/alice/trackio-metrics). Or,
from the server's command line:
docker compose exec -T api thinkingface admin token create alice \
--name claude-ocr --expires-in-days 7 --repo datasets/alice/trackio-metrics > agent-token.txt
The repository must exist before a token can be restricted to it — a restricted token cannot create repositories. Every request the token makes carries its id and name in the server's access log, so what the agent did is attributable. See Restricting a token to repositories.
The MCP server¶
tf mcp serves the tf experiments functionality over MCP's stdio transport. The agent's client
starts it as a subprocess and talks to it on stdin/stdout — there is no port to open and no daemon
to keep running. Install tf first (tf CLI).
Register it¶
With Claude Code, on a machine where tf login has already been run:
claude mcp add thinkingface -- tf mcp
To give it the agent's own token instead of your saved login, pass the endpoint and token as environment variables:
claude mcp add thinkingface \
-e THINKINGFACE_ENDPOINT=http://localhost:8080 \
-e THINKINGFACE_API_KEY=tf_xxxxxxxxxxxx \
-- tf mcp
Clients that take a JSON configuration — a project's .mcp.json, or any client with an
mcpServers map — use the same command and variables:
{
"mcpServers": {
"thinkingface": {
"command": "tf",
"args": ["mcp"],
"env": {
"THINKINGFACE_ENDPOINT": "http://localhost:8080",
"THINKINGFACE_API_KEY": "tf_xxxxxxxxxxxx"
}
}
}
}
tf mcp resolves credentials exactly like every other tf command: the --endpoint / --token
flags, then TF_ENDPOINT / THINKINGFACE_ENDPOINT / HF_ENDPOINT and THINKINGFACE_API_KEY /
TF_TOKEN / THINKINGFACE_TOKEN, then the login tf login saved (see the
credential resolution order). They are
resolved on the first tool call, not at startup, so a server started before you logged in keeps
running and answers each call with an error that says what to do until credentials exist.
tf mcp writes nothing to stderr unless started with --verbose, which logs credential resolution
and each request — useful when a client reports the server as failing.
Tools¶
repo is always the experiment repository as ns/name. Each tool answers with the API's JSON.
| Tool | Arguments (? = optional) |
Does |
|---|---|---|
list_experiment_repos |
author?, search? |
Lists the repositories that hold experiments |
list_projects |
repo |
A repository's projects, with run counts and metric goals |
list_runs |
repo, project, group?, status?[], tag?[], archived?, sort?, order?, limit? |
Runs, filtered and sorted on the server — sort takes the same specs as tf experiments runs --sort, including best:<metric> |
get_run |
repo, project, run |
One run: status, step, config, last / min / max of every metric |
get_metrics |
repo, project, runs[], keys?[], x? (step or time), max_points? |
Metric series as [x, y] pairs, downsampled to max_points (200 by default to keep answers small, at most 5000) |
wait_for_run |
repo, project, run, until?, timeout_seconds?, ignore_stale? |
Waits like tf experiments wait (until defaults to status!=running; timeout_seconds defaults to 600, at most 3600) |
config_diff |
repo, project, runs?[], include_meta? |
The config keys that differ between runs |
get_notes |
repo, project |
The project's NOTES.md, with its blob_sha |
update_notes |
repo, project, content, base_sha?, message? |
Replaces the notes |
annotate_run |
repo, project, run, note?, tags?[], archived? |
Changes only the fields given; tags replaces the list |
set_metric_goals |
repo, project, goals ({metric: "min" \| "max" \| ""}) |
Merges goals; "" removes one |
A few behaviours worth knowing when you read an agent's transcript:
- A
wait_for_runtimeout is a normal answer,met: falsewithreason: "timeout": the agent calls again to keep waiting.reason: "stopped"means the run finished, failed or went stale without meeting the condition, so waiting longer would not help. While one call waits, the others keep working. - Mistakes come back as tool results, not protocol errors — a missing argument, an
untilthat does not parse, a 403 from the server, a notes conflict — each with a sentence the model can act on, so the agent sees it and corrects itself. update_noteswith theblob_shafromget_notesasbase_shaturns a concurrent edit (a person editing the notes in the web UI at the same time) into a conflict the agent has to resolve, instead of a lost write. Withoutbase_shait overwrites.- The read-only tools are marked as such (
readOnlyHint), so a client can let them run without asking you each time.
A typical loop¶
The loop below is what an agent usually does with these tools; the same steps work from a shell with the CLI.
- Declare what "better" means once per project, so "best" is well defined:
set_metric_goalswith{"val/CER": "min"}, ortf experiments goals alice/trackio-metrics ocr val/CER=min. - Launch the training job with the trackio shim and a run name the agent chooses, for example
THINKINGFACE_REPO=alice/trackio-metrics python train.py --lr 3e-4 --run-name lr-3e-4. -
Wait for it — right away; the run does not have to exist yet:
wait_for_runwithuntil: "step >= 12000 or status != running", or on the command linetf experiments wait alice/trackio-metrics ocr lr-3e-4 \ --until 'step >= 12000 or status != running' --timeout 6h --jsonA job that crashes goes
staleabout two minutes after its last heartbeat, which ends the wait withreason: "stopped"instead of letting it run into the timeout. 4. Read the result in context:list_runswithsort: "best:val/CER"and a smalllimitfor the leaderboard,get_runfor the new run's last / min / max,config_diffto see what differs from the best run so far,get_metricswhen the curve's shape matters. 5. Write down what it learned:annotate_runwith a short note and a tag, andupdate_notesto add a line to the project notebook. A link written as[lr 3e-4](run:lr-3e-4)becomes a link to that run in the web UI, so the people reading the notes can click through to the curve.
The project notes live in the repository as {project}/NOTES.md, so the agent's findings are
versioned next to the metrics, visible on the project page, and reviewable like any other commit.
From a shell: --json everywhere¶
Every tf command except tf mcp itself — login, logout, status, whoami, up,
version and all of tf experiments — takes --json and then prints one JSON object on stdout. Progress, warnings and errors stay on
stderr, and the exit codes are unchanged (0 success, 1 failure, 2 usage error), so a script
or an agent can parse stdout without filtering it:
best=$(tf experiments runs alice/trackio-metrics ocr --sort best:val/CER --limit 1 --json |
jq -r '.runs[0].name')
tf experiments diff alice/trackio-metrics ocr "$best" lr-3e-4 --json
tf experiments wait adds its own exit codes (0 the condition holds, 1 timeout or the run
stopped), which makes it the natural thing to put between "start training" and "act on the result"
in a script. See the tf CLI reference for every command.
The HTTP API and its OpenAPI document¶
GET /api/openapi.json serves an OpenAPI 3.1 description of the programmatic surface: sign-in,
access tokens, the current user, server info, listing and creating repositories, and the whole
experiment API under /api/v1/experiments. Its servers entry is the instance's own TF_PUBLIC_URL,
so a client generated from it needs no editing:
curl -s http://localhost:8080/api/openapi.json | jq '.paths | keys'
The HuggingFace-compatible endpoints (apart from whoami-v2 and repository creation) and the
git / Git LFS transports are deliberately left out — huggingface_hub and git define those. Authenticate with Authorization: Bearer tf_..., exactly
as for any other API call. On an instance running with TF_REQUIRE_AUTH_FOR_READ, fetching the
document itself needs a token too.
Least privilege, in short¶
- Give each agent its own write token, restricted to the repository it works on and expiring — never your personal unrestricted token.
- Create the repository beforehand; a restricted token cannot.
- Let the client run the read-only tools without confirmation, and keep an eye on the ones that
write (
annotate_run,update_notes,set_metric_goals). - Remember that reads are instance-wide: a token restricts what an agent can change, not what it can see. Keep data an agent must not read on an instance it has no token for.
- Revoke the token at Settings → Access tokens when the work is done, or let it expire.
Related pages¶
- Tracking Experiments — logging runs, goals, notes, and the
tf experimentscommands - tf CLI —
tf mcpand every other command - Authentication — restricted tokens