Skip to content

Commit 5bcedda

Browse files
authored
Merge branch 'main' into fix/simplify-publish-dialog-version-names
2 parents ae399aa + bd89ad5 commit 5bcedda

188 files changed

Lines changed: 35129 additions & 21646 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/transformerlab-cli/SKILL.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ lab task list
8787
# 3. Queue a task on a compute provider
8888
# NOTE: --no-interactive silently picks the DEFAULT provider (Local).
8989
# To pick a specific provider, run interactively (see "Selecting a provider" below).
90-
lab task queue TASK_ID --no-interactive
90+
# ALWAYS pass --description/-m with a markdown note describing the iteration
91+
# (see "Always write a run description" below).
92+
lab task queue TASK_ID --no-interactive -m "Testing lr=3e-5 after loss plateaued at 2.1"
9193

9294
# 4. Monitor the job (three log streams — see "Job logs: three real commands" below)
9395
lab job list --running
@@ -226,6 +228,37 @@ lab task add ./hello-world-task --no-interactive
226228
10. **Never use the deprecated `lab job logs`** — see the "Job logs: three real commands" section below.
227229
11. **After queuing a task, ASK the user if they'd like you to watch the logs.** Don't start streaming or polling automatically — jobs can take minutes to hours, and `--follow` blocks. Report the Job ID and ask: "Want me to watch the logs and report back?"
228230
12. **Never create API keys programmatically** — if auth fails, ask the user to provide an API key from the web UI
231+
13. **Always pass `--description/-m` when queuing a task. Generate it yourself — never ask the user.** See "Always write a run description" below.
232+
233+
### Always write a run description
234+
235+
Every `lab task queue` call MUST include `--description/-m "..."`. The description is markdown stored on the job and shown in `lab job info`. Its audience is a future researcher reading `lab job list` weeks later — they have git and the task code, but NOT this chat. The description is the only bridge.
236+
237+
**Treat it like a short PR description for this run.** Draft 1–5 lines (bullets for multi-point notes) covering:
238+
239+
1. **What changed vs the prior run / baseline** — the concrete diff (hyperparameters, code, model, data, infra). If nothing changed, say so and link the prior job.
240+
2. **What hypothesis you're testing** — why this run is worth doing.
241+
3. **What a future reader should remember** — gotchas, prior surprises, things to check in the output.
242+
243+
Pull these from the conversation and recent git diff / edited files. If the note has newlines or shell-awkward characters, pipe it: `printf '%s' "$DESC" | lab task queue abc123 -m -`.
244+
245+
```bash
246+
# Good: diff + hypothesis + watch
247+
printf '%s' "- Bumped lr 1e-5 → 3e-5, warmup 100 → 500 steps.
248+
- Testing whether higher lr clears the eval/loss=2.1 plateau seen in job 7f21 around step 2k.
249+
- Watch: earlier runs with lr≥5e-5 diverged by step 500." | lab task queue abc123 --no-interactive -m -
250+
251+
# Good: small change — one line is enough
252+
lab task queue abc123 --no-interactive -m "Rerun on H100 (was A100) to confirm throughput regression from #1850."
253+
254+
# Good: nothing changed
255+
lab task queue abc123 --no-interactive -m "Rerun of job 7f21, no code or config changes (network flake on first attempt)."
256+
257+
# Bad: generic filler that tells the reader nothing
258+
lab task queue abc123 -m "train model"
259+
```
260+
261+
Don't restate the task name, full hyperparameter dict, or file paths — those are already on the job record. Don't copy the user's last message verbatim — synthesize. If the conversation is truly empty of signal, fall back to `"Rerun of <id>, no changes"`.
229262

230263
### Selecting a provider when queuing a task
231264

@@ -308,7 +341,7 @@ This applies to launching jobs, fetching logs, checking cluster status, and ever
308341
| `lab task init` | Scaffold `task.yaml` + `main.py` in the current directory (`--interactive` to prompt) | No |
309342
| `lab task add [dir]` | Add task from directory or `--from-git` URL (`--no-interactive`, `--dry-run`) | Yes |
310343
| `lab task delete <id>` | Delete a task (`--no-interactive` to skip confirmation) | Yes |
311-
| `lab task queue <id>` | Queue task on compute provider | Yes |
344+
| `lab task queue <id>` | Queue task on compute provider (`-m/--description` for a markdown run note; required for agents, see "Always write a run description") | Yes |
312345
| `lab task gallery` | Browse/import from task gallery | Yes |
313346
| `lab job list` | List jobs (`--running` for active only) | Yes |
314347
| `lab job info <id>` | Get detailed job information | Yes |

.agents/skills/transformerlab-cli/references/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Queue a task on a compute provider.
131131
| Option | Description |
132132
|---|---|
133133
| `--no-interactive` | Skip prompts. Uses the task's configured provider or first available. Parameters use defaults. **Always use this in automated workflows.** |
134+
| `-m`, `--description <text>` | Markdown note describing what this run is trying to accomplish (stored on the job, shown in `lab job info`). Pass `-` to read from stdin. **Agents: required per SKILL.md rule 13.** |
134135

135136
**JSON output:** Returns the created job object with `id` and `status`.
136137

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Gallery Channel Bundles
2+
3+
on:
4+
pull_request:
5+
branches: ["main"]
6+
paths:
7+
- "api/transformerlab/galleries/src/**"
8+
- "api/transformerlab/galleries/combine_subset_galleries.py"
9+
push:
10+
branches: ["main"]
11+
paths:
12+
- "api/transformerlab/galleries/src/**"
13+
- "api/transformerlab/galleries/combine_subset_galleries.py"
14+
15+
jobs:
16+
verify-bundle-updated:
17+
runs-on: ubuntu-latest
18+
defaults:
19+
run:
20+
working-directory: api
21+
steps:
22+
- uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
- name: Set up Python 3.11
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: "3.11"
29+
- name: Require stable/beta bundle update for source changes
30+
shell: bash
31+
run: |
32+
if [ "${{ github.event_name }}" = "pull_request" ]; then
33+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
34+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
35+
else
36+
BASE_SHA="${{ github.event.before }}"
37+
HEAD_SHA="${{ github.sha }}"
38+
fi
39+
40+
CHANGED="$(git diff --name-only "${BASE_SHA}" "${HEAD_SHA}")"
41+
echo "Changed files between ${BASE_SHA} and ${HEAD_SHA}:"
42+
printf '%s\n' "${CHANGED}"
43+
44+
if printf '%s\n' "${CHANGED}" | grep -E '^api/transformerlab/galleries/channels/(stable|beta)/latest/' > /dev/null; then
45+
echo "Bundle update detected under stable/beta latest."
46+
exit 0
47+
fi
48+
49+
echo "ERROR: galleries/src changed, but no stable/beta bundle update was committed."
50+
echo "Update either:"
51+
echo " - api/transformerlab/galleries/channels/stable/latest/*"
52+
echo " - api/transformerlab/galleries/channels/beta/latest/*"
53+
exit 1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,4 @@ test-results/
227227
**/.secrets
228228

229229
docs/superpowers/*
230+
.superpowers/

api/api.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555

5656

5757
from transformerlab.routers.experiment import experiment # noqa: E402
58-
from transformerlab.routers.experiment import jobs # noqa: E402
5958
from transformerlab.shared import shared # noqa: E402
6059
from transformerlab.shared import dirs # noqa: E402
6160
from lab.dirs import set_organization_id as lab_set_org_id # noqa: E402
@@ -306,7 +305,6 @@ async def validation_exception_handler(request, exc):
306305
app.include_router(serverinfo.router, dependencies=[Depends(get_user_and_team)])
307306
app.include_router(data.router, dependencies=[Depends(get_user_and_team)])
308307
app.include_router(experiment.router, dependencies=[Depends(get_user_and_team)])
309-
app.include_router(jobs.router, dependencies=[Depends(get_user_and_team)])
310308
app.include_router(config.router, dependencies=[Depends(get_user_and_team)])
311309
app.include_router(teams.router, dependencies=[Depends(get_user_and_team)])
312310
app.include_router(compute_provider.router)

api/localprovider_pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies = [
3232
"soundfile==0.13.1",
3333
"tensorboardX==2.6.2.2",
3434
"timm==1.0.15",
35-
"transformerlab==0.1.22",
35+
"transformerlab==0.1.24",
3636
"transformerlab-inference==0.2.52",
3737
"transformers==4.57.1",
3838
"wandb==0.23.1",

api/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies = [
2424
"python-dotenv==1.1.0",
2525
"python-multipart==0.0.20",
2626
"sqlalchemy[asyncio]==2.0.40",
27-
"transformerlab==0.1.23",
27+
"transformerlab==0.1.24",
2828
"transformerlab-inference==0.2.52",
2929
"uvicorn==0.35.0",
3030
"watchfiles==1.0.5",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
4+
5+
@pytest.mark.asyncio
6+
async def test_check_storage_probe_found():
7+
"""check_storage_probe returns found=True when sentinel file exists."""
8+
with patch("transformerlab.services.compute_provider.storage_probe_service.set_organization_id") as mock_set_org:
9+
with patch(
10+
"transformerlab.services.compute_provider.storage_probe_service.get_workspace_dir",
11+
new=AsyncMock(return_value="/mnt/nfs/orgs/team1/workspace"),
12+
):
13+
with patch("transformerlab.services.compute_provider.storage_probe_service.storage") as mock_storage:
14+
mock_storage.join = lambda *parts: "/".join(parts)
15+
mock_storage.exists = AsyncMock(return_value=True)
16+
17+
from transformerlab.services.compute_provider.storage_probe_service import check_storage_probe
18+
19+
result = await check_storage_probe(job_id="job-123", team_id="team1")
20+
21+
assert result["found"] is True
22+
assert "storage-probe-job-123.txt" in result["path"]
23+
mock_set_org.assert_any_call("team1")
24+
mock_set_org.assert_any_call(None)
25+
26+
27+
@pytest.mark.asyncio
28+
async def test_check_storage_probe_not_found():
29+
"""check_storage_probe returns found=False when sentinel file is missing."""
30+
with patch("transformerlab.services.compute_provider.storage_probe_service.set_organization_id"):
31+
with patch(
32+
"transformerlab.services.compute_provider.storage_probe_service.get_workspace_dir",
33+
new=AsyncMock(return_value="/mnt/nfs/orgs/team1/workspace"),
34+
):
35+
with patch("transformerlab.services.compute_provider.storage_probe_service.storage") as mock_storage:
36+
mock_storage.join = lambda *parts: "/".join(parts)
37+
mock_storage.exists = AsyncMock(return_value=False)
38+
39+
from transformerlab.services.compute_provider.storage_probe_service import check_storage_probe
40+
41+
result = await check_storage_probe(job_id="job-999", team_id="team1")
42+
43+
assert result["found"] is False
44+
45+
46+
@pytest.mark.asyncio
47+
async def test_launch_storage_probe_returns_job_id():
48+
"""launch_storage_probe calls launch_template_on_provider and returns job_id."""
49+
mock_launch = AsyncMock(return_value={"job_id": 42, "status": "success"})
50+
51+
with patch(
52+
"transformerlab.services.compute_provider.storage_probe_service.launch_template_on_provider",
53+
new=mock_launch,
54+
):
55+
from transformerlab.services.compute_provider.storage_probe_service import launch_storage_probe
56+
57+
user_and_team = {"team_id": "team1", "user": MagicMock(id="user1", first_name="", last_name="", email="")}
58+
result = await launch_storage_probe(
59+
provider_id="prov-1",
60+
user_and_team=user_and_team,
61+
session=MagicMock(),
62+
)
63+
64+
assert result["job_id"] == 42
65+
assert result["experiment_id"] == "__storage_probe__"
66+
call_args = mock_launch.call_args
67+
request = call_args.kwargs["request"]
68+
assert request.run == "python -m lab.probe"
69+
assert request.experiment_id == "__storage_probe__"
70+
assert request.cpus == "1"
71+
assert request.accelerators is None

api/test/shared/test_galleries.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from transformerlab.shared import galleries
2+
3+
4+
def test_channel_manifests_reject_too_new_min_version():
5+
manifest = {
6+
"channel": "stable",
7+
"min_supported_app_version": "0.30.0",
8+
}
9+
10+
assert galleries.is_manifest_version_compatible(manifest, "0.27.0") is False
11+
12+
13+
def test_channel_manifests_accept_matching_version_range():
14+
manifest = {
15+
"channel": "stable",
16+
"min_supported_app_version": "0.20.0",
17+
"max_supported_app_version": "0.29.0",
18+
}
19+
20+
assert galleries.is_manifest_version_compatible(manifest, "0.27.0") is True
21+
22+
23+
def test_only_selected_galleries_use_channel_fetch():
24+
assert galleries.should_use_channel_bundle(galleries.TASKS_GALLERY_FILE) is True
25+
assert galleries.should_use_channel_bundle(galleries.INTERACTIVE_GALLERY_FILE) is True
26+
assert galleries.should_use_channel_bundle(galleries.ANNOUNCEMENTS_GALLERY_FILE) is True
27+
assert galleries.should_use_channel_bundle(galleries.TEAM_TASKS_GALLERY_FILE) is False
28+
29+
30+
def test_local_channel_path_uses_selected_channel(monkeypatch, tmp_path):
31+
monkeypatch.setattr(galleries.dirs, "GALLERIES_LOCAL_FALLBACK_DIR", str(tmp_path))
32+
monkeypatch.setenv("TLAB_GALLERY_CHANNEL", "beta")
33+
channel_file = tmp_path / "channels" / "beta" / "latest" / galleries.TASKS_GALLERY_FILE
34+
channel_file.parent.mkdir(parents=True, exist_ok=True)
35+
channel_file.write_text("[]", encoding="utf-8")
36+
37+
resolved = galleries.get_local_gallery_path(galleries.TASKS_GALLERY_FILE)
38+
assert resolved.endswith("channels/beta/latest/task-gallery.json")

0 commit comments

Comments
 (0)