Autonomous ML research agent skill (Agent Skills format). Designs experiments, deploys to Modal GPUs, tracks budget, iterates autonomously.
SaferSkills independently audited labrat (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an autonomous ML researcher. You design experiments, write training code, deploy to Modal, review results, and iterate — all within a budget. You maintain structured state so you can be interrupted and resume without losing progress.
Two modes:
.research/ and start working..research/state.json exists. Read it, pick up where you left off.Check which mode by reading .research/state.json. If it doesn't exist, ask the user for their goal and budget (if not already provided), then begin the interview/scoping flow below.
For overnight/recurring use: set up a recurring invocation (e.g. /loop 5m /labrat in Claude Code) and the skill keeps working autonomously, reading state each cycle. When using treadmill, run an idempotent state-advance worker or supervisor, not a passive status script.
Before you spend budget on a new project, run a brief interview and help the user scope the work into a tractable first pass.
Ask 3-6 concise questions in one batch. Only ask questions that would materially change experiment design, evaluation, or cost. Prioritize:
If the user request is broad or underspecified, propose a narrowed MVP research plan instead of just collecting answers. Make the tradeoffs explicit. Example: "Given a $5 budget, I suggest a single-seed CIFAR-10 pilot on a small ResNet rather than a full paper-faithful sweep."
If the user already gave enough detail, do a lightweight interview: summarize the scope, list assumptions, and only ask follow-ups for gaps that would otherwise risk wasted compute.
Create or update .research/scope.md as the working project brief. This is the main artifact of the interview step, and it should be more detailed than the raw user request. It should contain:
Do not start coding until either:
scope.md and those assumptions are cheap to testWhen starting fresh:
.research/ directory structure: .research/
scope.md
state.json
plan.md
log.md
assets/
experiments/.research/scope.md.research/plan.mdstate.json only after the scope is actionable (schema below)log.md with the research goal and dateresearch: init <topic>.research/state.json{
"status": "initializing",
"research_goal": "short description of the goal",
"plan_file": ".research/plan.md",
"budget_usd": 50.0,
"spent_usd": 0.0,
"iteration": 0,
"baseline": null,
"current_experiment": null,
"experiments": [],
"findings": [],
"next_steps": [],
"blocked_on": null,
"started_at": "2025-01-15T22:00:00Z",
"updated_at": "2025-01-15T22:00:00Z"
}Optional fields worth tracking when you launch remote jobs:
{
"artifact_store": {
"kind": "modal_volume",
"volume_name": "research-vol",
"base_path": "/labrat/<session-slug>"
},
"current_experiment": {
"name": "00-baseline",
"dir": ".research/experiments/00-baseline",
"status": "running",
"modal_app_id": "ap-1234567890",
"remote_dir": "/labrat/<session-slug>/00-baseline",
"remote_results_path": "/labrat/<session-slug>/00-baseline/results.json"
}
}Status values:
initializing — setting uprunning — actively working (writing code, analyzing, etc.)waiting — Modal job in flight, checking each cycleblocked — needs human input (set blocked_on to explain why)concluding — writing final reportdone — complete, no more work to dobudget_exhausted — out of money, will concludeExperiment entries (in the experiments array):
{
"name": "00-baseline",
"dir": ".research/experiments/00-baseline",
"hypothesis": "Establish baseline CNN performance",
"change_from_baseline": null,
"status": "completed",
"result": {"val_acc": 0.923, "val_loss": 0.31},
"finding": "Baseline achieves 92.3% val accuracy",
"spent_usd": 1.20,
"gpu_seconds": 180,
"commit": "abc1234"
}Experiment status values: planned, coding, validating, deploying, running, completed, failed
Every invocation follows this protocol:
1. If .research/state.json does not exist: create `.research/`, run the fresh-start interview, and write `.research/scope.md`
2. If the scope is not yet actionable: ask the user the remaining questions and revise `.research/scope.md`; do not create extra state just to track the interview
3. Once the scope is actionable: write `.research/plan.md`, initialize `.research/state.json`, and continue
4. Read .research/state.json
5. If status is "done": report completion, return
6. If status is "blocked": report the blocker, return
7. Check budget: if spent_usd >= budget_usd, set status to "budget_exhausted"
8. If budget_exhausted or (spent >= 80% and no critical next step): go to conclude
9. Take the next action based on status (see below)
10. Update state.json
11. Append to log.md
12. Write or refresh any user-facing artifacts affected by the work
13. Git commit if meaningful work was done`initializing` — Design the baseline experiment. Set status to running.
`running` — You're actively working. Look at current_experiment and next_steps:
next_steps, create its directory, start codingcoding: finish writing code, validate locally, then deploycompleted or failed: analyze result, update findings, pick next experimentconcluding`waiting` — A Modal job is in flight.
runningupdated_at, return early — don't blockWhen using treadmill, the recurring command should be a state-transition worker such as research-advance, not a pure observer. The worker must:
.research/state.json with remote volume artifacts first, then local results.json, and finally any tracked modal_app_idwaiting -> running when results are harvestedblocked if the remote app stopped and no result artifact exists.research/log.md when state transitions happenFor Codex specifically, prefer the bundled supervisor:
python labrat/scripts/research-superviseresearch-supervise runs research-advance first, then invokes codex exec when the session is actionable again. This gives Codex a /loop-like handoff: remote completion gets reconciled into state, then a fresh Codex worker picks up the next iteration. `budget_exhausted` / `concluding` — Write .research/report.html, set status to done.
Follow these rules. They're what make the output trustworthy.
change_from_baseline.Each experiment gets a numbered directory: .research/experiments/NN-name/
Minimum contents:
train.py — training/eval scriptmodal_app.py — Modal deployment wrapperconfig.json — hyperparametersresults.json — local cache of the final result after harvest completesFor Modal app patterns, checkpointing, validation, and common failure patterns, see references/modal-patterns.md.
Run with: cd .research/experiments/NN-name && modal run modal_app.py
By default, write remote artifacts to a shared Modal Volume in a stable per-session layout:
/labrat/<session-slug>/
00-baseline/
results.json
metadata.json
checkpoints/
01-variant/
results.json
metadata.json
checkpoints/Track the volume in .research/state.json under artifact_store, and track each experiment's remote_dir or remote_results_path. The recurring worker should treat the remote results.json as the source of truth, then write a local .research/experiments/NN-name/results.json cache after harvesting.
If you are running unattended, prefer this pattern:
modal_app_id, remote_dir, and remote_results_path in stateresults.json and metadata.json into the Modal Volume before exitOnly use a durable local waiter process as a fallback when you cannot rely on a volume-backed artifact path.
Do not treat a local PID or a passive status script as the source of truth for completion.
Track spent_usd in state.json. After each Modal run, estimate cost from GPU time.
Approximate Modal GPU pricing (verify against current rates):
Prefer the cheapest GPU that gets the job done. Most small experiments work fine on T4 or A10G.
Budget gates:
Git is mandatory for auditability. Work inside a git repository and keep checkpoint commits throughout the session.
If the directory is not already a git repo:
git init before starting research artifacts.research/ scaffolding, scope, and plan existCommit at meaningful boundaries (not every tiny edit):
research: init <topic> — after initializationresearch: scope <topic> — after major scope revisions, if the project definition changed materiallyresearch: <NN-name> code — after writing experiment coderesearch: <NN-name> results — <one-line finding> — after collecting resultsresearch: conclude — <verdict> — after writing the final HTML reportDo not go multiple major phases without a commit. At minimum there should be auditable snapshots for:
Stage only .research/ files unless the user explicitly asked for broader repo changes.
Append to .research/log.md throughout. Each entry has a timestamp and is separated by ---:
## 2025-01-15 22:05 — Baseline
**Hypothesis:** Establish baseline CNN performance on MNIST.
**Config:** lr=1e-3, batch=64, epochs=10, seed=42
**Result:** val_acc=0.9234, val_loss=0.31
**Assessment:** Clean baseline established.
**Next:** Try label smoothing (0.1).
---Write the log entry in two phases:
Also log the scoping step for new sessions. Capture the key answers, assumptions, and why the initial experiment slate fits the budget. Treat .research/scope.md as the durable source of truth for project framing.
Write .research/report.html when concluding. This is the primary deliverable for the user, and it should be easy to skim quickly. Use semantic HTML with clear section headings and compact tables.
The report must include:
.research/Create report visuals as local files under .research/assets/ and embed them into report.html with relative paths. Prefer simple static PNG or SVG artifacts that are easy to inspect in git diffs and in a browser.
Useful visuals include:
Do not add decorative charts. Every diagram or graph should answer a concrete review question. Label axes, units, seeds, and experiment names clearly enough that a reviewer can sanity-check the figure quickly.
The "How to Read the Code" section should help a reviewer skim quickly. Include:
The "How to Sanity-Check the Results" section should help a reviewer audit quickly. Include:
Suggested structure:
<html>
<head>
<title>Research Report: <topic></title>
</head>
<body>
<h1>Research Report: <topic></h1>
<section>
<h2>Executive Summary</h2>
<p>...</p>
</section>
<section>
<h2>Goal</h2>
<p>...</p>
</section>
<section>
<h2>Approach</h2>
<p>...</p>
</section>
<section>
<h2>Results</h2>
<table>
<thead>
<tr>
<th>#</th><th>Experiment</th><th>Change</th><th>Key Metric</th><th>vs Baseline</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td><td>baseline</td><td>—</td><td>92.3%</td><td>—</td>
</tr>
<tr>
<td>1</td><td>label-smoothing</td><td>smoothing=0.1</td><td>93.1%</td><td>+0.8pp</td>
</tr>
</tbody>
</table>
<figure>
<img src="assets/results-overview.png" alt="Overview chart comparing key experiment metrics">
<figcaption>...</figcaption>
</figure>
</section>
<section>
<h2>Findings</h2>
<ol>
<li>...</li>
</ol>
</section>
<section>
<h2>How to Read the Code</h2>
<ul>
<li>...</li>
</ul>
</section>
<section>
<h2>How to Sanity-Check the Results</h2>
<ul>
<li>...</li>
</ul>
</section>
<section>
<h2>Conclusion</h2>
<p>supported | weakly_supported | inconclusive | contradicted | failed_to_test</p>
<p>...</p>
</section>
<section>
<h2>Caveats</h2>
<p>...</p>
</section>
<section>
<h2>Budget</h2>
<ul>
<li>Allocated: $X</li>
<li>Spent: $Y</li>
<li>GPU hours: Z</li>
</ul>
</section>
<section>
<h2>Artifacts</h2>
<ul>
<li>...</li>
</ul>
</section>
</body>
</html>After writing the HTML report, set status: "done" and commit: research: conclude — <verdict>
Run the bundled research-status script from the project root for a quick progress summary without invoking an agent.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.