Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ outputs/
# Local artifacts
tinker.db

# Large local Toolathlon run data. Ray's runtime-environment packager honors
# .gitignore, so these must stay out of the distributed working-directory zip.
/jobs/
/toolathlon-tasks/
/toolathlon-tasks.zip
/data/toolathlon-harbor/

# Alembic - don't track pycache
tx/tinker/alembic/__pycache__/

Expand Down
47 changes: 40 additions & 7 deletions examples/train/toolathlon_harbor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ export TOOLATHLON_API_KEY=dummy
export TOOLATHLON_MODEL=Qwen3-8B

bash examples/train/toolathlon_harbor/run_eval.sh \
-i 401k-watchlist-recency-window-refresh
-i q3-eta-leaderboard-definition-aware-reconcile
```

`run_eval.sh` uses Harbor's local Docker environment by default. Use the
separate `run_compute_eval.sh` wrapper only when task containers should run on
AfterQuery Compute.

The default task root is `toolathlon-tasks/tasks`. Override it with
The default evaluation root is `toolathlon-tasks/eval_tasks`, which contains the 100 held-out tasks listed in `toolathlon-tasks/samples/pass-at-4-100.txt`. Override it with
`TOOLATHLON_TASKS_DIR`. The launcher builds `toolathlon-json-runtime:v1` from
the bundled runtime archive as `linux/amd64` when the image is missing.
Additional arguments are forwarded to `harbor run`, so omitting `-i` runs the
Expand Down Expand Up @@ -56,7 +56,7 @@ echo "$TOKEN" | docker login us-docker.pkg.dev \
-u oauth2accesstoken --password-stdin

bash examples/train/toolathlon_harbor/run_compute_eval.sh \
-i 401k-watchlist-recency-window-refresh
-i q3-eta-leaderboard-definition-aware-reconcile
```

`run_compute_eval.sh` exits immediately with an explanatory error when
Expand All @@ -74,8 +74,8 @@ The reference and no-op paths do not call a model:

```bash
cd toolathlon-tasks
harbor run --path tasks -i 401k-watchlist-recency-window-refresh --agent oracle
harbor run --path tasks -i 401k-watchlist-recency-window-refresh --agent nop
harbor run --path eval_tasks -i q3-eta-leaderboard-definition-aware-reconcile --agent oracle
harbor run --path eval_tasks -i q3-eta-leaderboard-definition-aware-reconcile --agent nop
```

Expect rewards `1.0` and `0.0`, respectively.
Expand All @@ -87,8 +87,8 @@ Registry as above and run:
COMPUTE_API_URL=${COMPUTE_API_URL:-https://compute-api.afterquery.com} \
COMPUTE_IMAGE_REGISTRY=${COMPUTE_IMAGE_REGISTRY:-us-docker.pkg.dev/afterquery-compute/compute-images/} \
uv run --extra harbor harbor run \
--path toolathlon-tasks/tasks \
-i 401k-watchlist-recency-window-refresh \
--path toolathlon-tasks/eval_tasks \
-i q3-eta-leaderboard-definition-aware-reconcile \
--agent oracle \
--env compute \
--n-concurrent 1
Expand All @@ -99,6 +99,39 @@ replay, and verification without making a model request.

## SkyRL generation and training

### Shared Daytona runtime (no local Docker required)

For Daytona evaluation or GRPO, build the runtime once as a named snapshot on
Daytona itself. The build happens remotely and does not require a Docker daemon
on the SkyRL host:

```bash
export DAYTONA_API_KEY=...
uv run --extra harbor python \
examples/train/toolathlon_harbor/create_daytona_runtime_snapshot.py \
--archive toolathlon-tasks/runtime/toolathlon-json-runtime-src.tar.gz \
--name toolathlon-json-runtime-v1
```

Stage the task dataset once on storage visible to the Ray workers. Staged tasks
contain `environment/task/` and `environment/mcp.json`, but no Dockerfile. Their
`task.toml` selects the shared runtime image, `/opt` upload workdir, and the
task-specific `T3_SERVERS` value:

```bash
uv run python examples/train/toolathlon_harbor/prepare_daytona_tasks.py \
--source toolathlon-tasks/tasks \
--output "$HOME/data/toolathlon-harbor/tasks" \
--runtime-image us-docker.pkg.dev/afterquery-compute/compute-images/toolathlon-json-runtime:v1
```

The named Daytona snapshot is the normal startup path. The registry image is a
fallback and must be built and pushed by CI or another Docker-capable machine;
this host does not need to build it. The two-node launcher stages the dataset
automatically when `TOOLATHLON_TASKS_DIR` is absent. Override
`TOOLATHLON_RUNTIME_IMAGE` or `DAYTONA_SNAPSHOT_TEMPLATE` to select another
version.

Use the existing Harbor entrypoint with this adapter's trial configuration and
the task directory as the dataset:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Build the shared Toolathlon runtime remotely as a named Daytona snapshot."""

from __future__ import annotations

import argparse
import asyncio
import tarfile
import tempfile
from pathlib import Path


async def create_snapshot(archive: Path, name: str, target: str | None) -> None:
from daytona import (
AsyncDaytona,
CreateSnapshotParams,
DaytonaConfig,
Image,
Resources,
)

config = DaytonaConfig(target=target) if target else None
daytona = AsyncDaytona(config) if config else AsyncDaytona()
try:
try:
existing = await daytona.snapshot.get(name)
except Exception as error:
if (
"not found" not in str(error).lower()
and "notfound" not in type(error).__name__.lower()
):
raise
else:
print(
f"Snapshot {name!r} already exists in state {existing.state}; nothing to do"
)
return

with tempfile.TemporaryDirectory(prefix="toolathlon-runtime-") as temporary:
root = Path(temporary)
with tarfile.open(archive, "r:gz") as bundle:
bundle.extractall(root, filter="data")
dockerfiles = list(root.glob("*/Dockerfile"))
if len(dockerfiles) != 1:
raise RuntimeError(
f"expected one runtime Dockerfile in {archive}, found {len(dockerfiles)}"
)
await daytona.snapshot.create(
CreateSnapshotParams(
name=name,
image=Image.from_dockerfile(str(dockerfiles[0])),
resources=Resources(cpu=2, memory=4, disk=20),
)
)
print(f"Submitted remote Daytona build for snapshot {name!r}")
finally:
await daytona.close()


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--archive", type=Path, required=True)
parser.add_argument("--name", required=True)
parser.add_argument("--target", help="optional Daytona target, for example us")
args = parser.parse_args()
asyncio.run(create_snapshot(args.archive.resolve(), args.name, args.target))


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ agent:
deadline_sec: 7000
request_timeout_sec: 900
max_tokens: 8192
max_context_tokens: 131072
context_manager_factory: examples.train.toolathlon_harbor.toolathlon_context_manager:create_managed_context
temperature: 1.0
collect_rollout_details: true
strict_rollout_details: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Harbor TrialConfig for context-managed Toolathlon GRPO rollouts on Daytona.
# SkyRL injects task.path, agent.model_name, api_base, and session_id per trial.

trials_dir: ~/toolathlon_grpo_qwen38_27b/trials

agent:
name: null
import_path: examples.train_integrations.harbor.mcp_agent:HarborMCPAgent
override_timeout_sec: 7200
mcp_servers:
- name: toolathlon
transport: stdio
command: python
args: ["-m", "t3.mcp_server"]
env:
OPENAI_API_KEY: "${TOOLATHLON_API_KEY:-dummy}"
kwargs:
max_turns: 64
deadline_sec: 7000
request_timeout_sec: 900
max_tokens: 8192
max_context_tokens: 131072
context_manager_factory: examples.train.toolathlon_harbor.toolathlon_context_manager:create_managed_context
temperature: 0.7
collect_rollout_details: true
strict_rollout_details: true

environment:
type: daytona
force_build: false
delete: true
override_cpus: null
override_memory_mb: null
override_storage_mb: null
kwargs:
# One runtime snapshot is built remotely; task files are uploaded into /opt.
snapshot_template_name: toolathlon-json-runtime-v1
auto_snapshot: false
auto_stop_interval_mins: 30

verifier:
disable: false
2 changes: 2 additions & 0 deletions examples/train/toolathlon_harbor/harbor_trial_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ agent:
deadline_sec: 7000
request_timeout_sec: 900
max_tokens: 8192
max_context_tokens: 131072
context_manager_factory: examples.train.toolathlon_harbor.toolathlon_context_manager:create_managed_context
temperature: 1.0
collect_rollout_details: true
strict_rollout_details: true
Expand Down
Loading
Loading