Skip to content

Provision Toolathlon service runtime sidecars#889

Merged
bingran-you merged 17 commits into
mainfrom
bry/toolathlon-poste-sidecar
Jul 5, 2026
Merged

Provision Toolathlon service runtime sidecars#889
bingran-you merged 17 commits into
mainfrom
bry/toolathlon-poste-sidecar

Conversation

@bingran-you

Copy link
Copy Markdown
Collaborator

Summary

  • Add pre-compose hooks for Docker and Daytona DinD sandboxes.
  • Materialize Toolathlon official service runtimes for Poste, WooCommerce, Canvas, and k8s/kind tasks.
  • Reseed Canvas users/admin tokens on first boot and verify the exact admin course-list API before marking Canvas ready.
  • Make WooCommerce multisite uploads writable for cover-image/product setup.
  • Ignore local Toolathlon validation artifacts and token caches.

Validation

  • uv run pytest tests/test_source_adapters.py tests/test_daytona_dind_compose_up.py tests/test_sandbox.py::TestDockerExecEnvSecrecy -q -> 31 passed
  • uv run ruff check src/benchflow/adapters/_toolathlon.py src/benchflow/adapters/_toolathlon_services.py src/benchflow/adapters/source.py src/benchflow/sandbox/docker.py src/benchflow/sandbox/daytona_dind.py tests/test_source_adapters.py tests/test_daytona_dind_compose_up.py tests/test_sandbox.py -> passed
  • Source probe: woocommerce-update-cover with --agent oracle --sandbox docker completed environment setup and reached expected missing-oracle boundary (environment_setup=142.6s).
  • Source probe: canvas-list-test with --agent oracle --sandbox docker completed environment setup and reached expected missing-oracle boundary (environment_setup=84.4s).
  • Source probe: k8s-mysql with --agent oracle --sandbox docker completed environment setup and reached expected missing-oracle boundary (environment_setup=66.4s).

Notes

  • The source probes use upstream Toolathlon main at b7bbac3f9a1f381b095c878debe1a47dd164ad85.
  • Upstream Toolathlon official tasks do not ship oracle/solve.sh; the expected terminal error for these infrastructure probes is the missing-oracle error after setup completes.

…ifier scoring

Follow-up to #885. Makes the materialized Toolathlon tasks actually runnable.

1. Token spawn-time resolution. The ${token.X}->${TOOLATHLON_X} env rewrite
   could never resolve per-task or runtime-computed tokens (Drive folder ids
   written by preprocess, groundtruth allowlists, per-student Canvas tokens):
   MCP env/args pass through verbatim and args cannot be resolved from env at
   all. New _toolathlon_container.py ships into each container: `write-config`
   bakes the global token_key_session.py from injected TOOLATHLON_* secrets;
   `launch` wraps every MCP server and resolves ${token.X}/${config}/paths
   across argv AND env at spawn, mirroring upstream utils/mcp/tool_servers.py
   (global merged with per-task override). Dep-free via an addict shim; absolute
   interpreter + PATH fallback so FastMCP can spawn it.

2. Pre-create declared server dirs. arxiv_local (--storage-path) and the emails
   server create working dirs lazily; an agent that reaches the goal another way
   leaves them absent and evaluators that os.listdir them crash. The launcher
   mkdir -p's an explicit allow-list of directory-valued flags.

3. Exit-code verifier scoring. Upstream evaluators score by exit code and
   several signal task-failure by raising (e.g. raise ValueError("Some tests
   FAILED")) rather than returning False. Treat any non-zero evaluator exit as
   reward 0 (healthy fail); only escalate to a verifier error on signatures that
   cannot be an agent failure (ModuleNotFoundError/ImportError/PermissionError).

4. Tolerate task_config entries naming local harness tools (e.g. web_search)
   with no MCP yaml instead of failing the whole adaptation.

Validated on Daytona+OpenHands+Azure gpt-5.5: 37/58 headless tasks healthy on
first pass; find-alita-paper and google-cloud/maps/github/hf probes all reward
1.0. 9 adapter tests, ruff + ty clean.
… access

Upstream preprocess/eval read all_token_key_session via attributes
(all_token_key_session.github_token) because upstream builds it with
addict.Dict. The generated global token_key_session.py used a plain dict, so
every github/HF/woocommerce/notion/snowflake task whose preprocess reads a
token that way crashed with AttributeError. Emit a self-contained
attribute-accessible dict subclass.
…ass launch_time

Two upstream-parity fixes to the preprocess/verifier invocation:
- Run as tasks.finalpool.<task>.preprocess.main (and .evaluation.main) from
  /workspace, exactly as upstream (uv run -m <dotted path>), so 'from ..utils'
  relative imports resolve. Running it as a bare 'preprocess.main' broke them.
- Pass --launch_time (a single value shared by preprocess and the verifier via
  /workspace/.toolathlon/launch_time.txt), matching upstream, but only to
  scripts that declare it (argparse errors on unknown args otherwise).
…al config

The notion preprocess (utils.app_specific.notion.notion_remove_and_duplicate)
reads source_notion_page_url / eval_notion_page_url off the global
all_token_key_session, but the container helper only baked the two integration
keys — the URLs resolved to None and every notion task's setup crashed before
duplicating the target subpage. Forward TOOLATHLON_NOTION_SOURCE_PAGE_URL and
TOOLATHLON_NOTION_EVAL_PAGE_URL through the token env allowlist and add both to
_GLOBAL_TOKENS. Bumps adapter version to invalidate the materialization cache.
… credential

Snowflake tasks need configs/snowflake_rsa_key.p8 for keypair auth, but the
credential-injection path validated every payload as JSON and chmod'd 0644 —
wrong for a PEM private key. Add content_format ('json'|'pem') and file_mode to
the credential spec: PEM payloads skip JSON validation and land at 0600. Register
the .p8 spec (b64_env TOOLATHLON_SNOWFLAKE_RSA_KEY_B64, trigger server snowflake).
Removes the last adapter blocker for the 4 Snowflake tasks (account still needs
a human trial signup).
Unblocks the poste-email Toolathlon tasks (7 poste-only fully; 16 more
partially, pending canvas/woo/notion/snowflake sidecars). The official
variant is single-container, so tasks that talk to a poste.io mail server
at localhost never ran. A new _toolathlon_services module emits an
environment/docker-compose.yaml with a poste sidecar (which auto-routes
the task to the Daytona DinD compose strategy), seeds the 503-user roster
(vendored), re-applies poste's plaintext-auth on each boot, and rewrites
the task's email configs to reach the sidecar before preprocess seeds.

Proven end-to-end on Daytona DinD (apply-phd-email -> clean reward 0.0,
no infra error). Bumps _ADAPTER_VERSION to bust the materialization cache.
The notion_official MCP server (npx mcp-remote https://mcp.notion.com/mcp)
needs an OAuth token to duplicate the source page during preprocess. Inject
the pre-authorized mcp-remote token dir (base64 via TOOLATHLON_NOTION_MCP_AUTH_B64)
into /workspace/configs/.mcp-auth at setup, pin mcp-remote to the version whose
on-disk token path matches, and make MCP_REMOTE_CONFIG_DIR absolute. Unblocks
the 8 notion tasks.
mcp-remote dynamically re-registers a new OAuth client on each run, which
invalidates the injected token and forces an interactive re-auth (blocking
notion tasks in the sandbox). Pass the pre-registered client via
--static-oauth-client-info so mcp-remote reuses it and refreshes the token
headlessly. Verified: 'Proxy established successfully' with an expired token.
…in-container

notion_official isn't in tasks' needed_mcp_servers — the preprocess spawns
mcp-remote itself from configs/mcp_servers/notion_official.yaml. So detect
notion tasks by the 'notion' (token) server they all list, and patch that
yaml in-container (pin version, absolute auth dir, --static-oauth-client-info)
so the preprocess page-duplication reuses the injected token headlessly.
…ntainer yaml dep)

The container's /usr/bin/python3 lacks PyYAML, so parse-and-patch failed.
Overwrite the yaml wholesale with a known-good headless-reuse config instead,
and suppress the macOS-xattr tar warning on the auth-dir extract.
@bingran-you
bingran-you temporarily deployed to pypi-internal-preview July 5, 2026 01:43 — with GitHub Actions Inactive

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d98439dbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +215 to +219
ports:
- "10005:80"
- "2525:25"
- "1143:143"
- "1587:587"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid fixed host port binds for non-k8s Poste tasks

For ordinary email tasks, poste_config_rewrite_command rewrites the task configs to reach the sidecar as poste on its internal ports, so these host bindings are unused; Docker documents HOST:CONTAINER port mappings as binding the first port on the Docker host (https://docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/). When two local Docker rollouts with Poste sidecars run concurrently, both compose projects try to reserve 10005/2525/1143/1587 and one compose up fails before the agent starts. Only publish fixed host ports for the k8s/host-network case, or keep non-k8s email tasks on the compose-internal network.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 515ae08: ordinary Poste sidecars no longer publish fixed host ports; those bindings are only emitted when the k8s host-network runtime is also required. Added a regression assertion that email-only tasks have no ports block, while the k8s+Poste test still requires the fixed mappings.

@bingran-you
bingran-you force-pushed the bry/toolathlon-poste-sidecar branch from 5d98439 to a39687d Compare July 5, 2026 01:49
@bingran-you
bingran-you temporarily deployed to pypi-internal-preview July 5, 2026 01:49 — with GitHub Actions Inactive
@bingran-you
bingran-you force-pushed the bry/toolathlon-poste-sidecar branch from a39687d to 89e9d6c Compare July 5, 2026 01:55
@bingran-you
bingran-you temporarily deployed to pypi-internal-preview July 5, 2026 01:55 — with GitHub Actions Inactive
@bingran-you
bingran-you temporarily deployed to pypi-internal-preview July 5, 2026 02:06 — with GitHub Actions Inactive
@bingran-you
bingran-you merged commit 0bf0b37 into main Jul 5, 2026
9 checks passed
@bingran-you
bingran-you deleted the bry/toolathlon-poste-sidecar branch July 5, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant