Repository navigation
Reinforcement learning and Frontier AI tutorials, and SkyRL moves into Reinforcement learning - #1938
Conversation
…design, self-play league Add three tutorials to the Reinforcement learning section, each validated end to end on a Union cluster, with report and artifact screenshots from the validated runs: - Teach a humanoid to run: Brax PPO over MuJoCo MJX, 8,192 G1 robots on one GPU. - Reward design with Claude and sandboxes: an Eureka-style reward search with every candidate trained in a unionai-sandbox session. - Self-play league for Connect Four: a league of past snapshots, Elo, caching, and an app to play the champion. Rewrite the section index with a comparison table, update the tutorials index card, and link the new tutorials from the user guide. Point unionai-examples at the commit that adds the examples (unionai/unionai-examples#348). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com>
…tion Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com>
GHA build & deploy previewBuilt by
Updated automatically on every push. |
There was a problem hiding this comment.
The three tutorials match their example code on the values I checked: resources, reuse policies, cache, retries, seeds, step counts and artifact names. The flyte 2.13.0 APIs they use exist. A few things need fixing before a reader can follow them cleanly.
- Eureka Getting started fails as written.
eureka.pyimportsunion.sandboxat module level, but the page never says to installunionai-sandbox(a pinned beta) or the SDK (inline comment). - Code is pinned to an unmerged commit. The code and the "Code available on GitHub" links depend on unionai-examples#348 merging first (question).
- Humanoid page contradicts itself. "A billion steps takes under 2 hours" is slower than that at the page's own 150M-in-19-min rate.
- Arena app is public. It deploys with
requires_auth=False, and the page doesn't say so. - Style rule. Two sections tell the history of earlier versions, which
style.mdrules out.
Not verified: the sandbox's isolation claims and session startup time, the Anthropic API options, and the run results.
What was checked (14 checks; 5 failed to run or not checked)
- ✓ Eureka page claims (pool size, CPUs, seeds, steps, memory cap, timeout, secret on driver only, include of harness.py, flyte.group, Candidates tab, --smoke) against unionai-examples eureka.py at the pinned commit
- ✓ Humanoid page claims (L40S, JAX 0.9 pin, preallocation env vars, three environments, njmax change, artifact name, --smoke/--checkpoint) against learn_to_run.py and g1.py (The smoke duration on the page ("a few minutes") differs from the code's "about 8 minutes".)
- ✓ Self-play page claims (ReusePolicy replicas=1, cache="auto" on training and matches, retries=2 on driver, 2,048 games x 25 iterations, 400 games per match, top-3 opponents, Elo base-10/400 scale, 231 x 400 = 92,400 games) against league.py, elo.py, app.py
- ✓ flyte 2.13.0 release exists, and APIs used exist at v2.13.0: ReusePolicy fields, TaskEnvironment include/depends_on, produces_artifacts, artifacts.Metadata.create_model_metadata, flyte.remote.Artifact.get(name) (Checked against the flyte-sdk tag v2.13.0.)
- – RunOutput semantics for 'latest successful league run' (The SDK docstring says 'latest output'. Resolution happens in the backend. Raised as a question.)
- ✗ Getting started steps runnable as written (local imports of each entry script) (eureka.py needs unionai-sandbox installed locally. The other two import only flyte and local modules at top level.)
- ✗ Internal consistency of results numbers (humanoid billion-step estimate, eureka 130 runs, self-play match count) (The billion-step estimate contradicts the page's own throughput.)
- ✗ Repo rules in .claude/rules/style.md, authoring.md, contributing.md (History narration in two sections.)
- ✓ Relative link targets in the new and changed pages exist in content/
- ✓ Hidden text or instructions addressed to automated reviewers in diff or PR description (None found.)
- – Sandbox behavior (network blocked, filesystem allow-list, PermissionError on service-account token, sessions opening in tens of ms, userns requirements) (The unionai-sandbox source wasn't available to me.)
- – Anthropic API options used in propose_rewards (server-side fallback beta, output_config)
- – Validated run results (speeds, Elo, success rates, timings) and screenshots (These need a cluster run.)
- – Docs build, markdownlint, cspell, check-links (Not run. The PR says they pass.)
Docsy, Union.ai's documentation agent. Advice only: it never approves or blocks.
| flyte create secret anthropic-api-key | ||
| git clone https://github.com/unionai/unionai-examples | ||
| cd unionai-examples/v2/tutorials/eureka_reward_design | ||
| python eureka.py # 4 generations of 6 candidates, about 25 minutes |
There was a problem hiding this comment.
eureka.py does from union import sandbox as sb at module level. So python eureka.py fails with an ImportError unless unionai-sandbox (pinned in the script header to the beta 0.0.1b15) is installed locally next to flyte==2.13.0. The page doesn't mention installing either one, or flyte create config. The GRPO tutorial in this section lists those prerequisites. Either add them, or run it with uv run eureka.py, which installs the script's inline dependencies.
| python eureka.py # 4 generations of 6 candidates, about 25 minutes | |
| uv run eureka.py # 4 generations of 6 candidates, about 25 minutes |
There was a problem hiding this comment.
Fixed in 7dfd913. Getting started uses uv run eureka.py, which installs the script's inline header, including unionai-sandbox==0.0.1b15. The page says that pin is a pre-release and links flyte create config.
| ## Going further | ||
|
|
||
| - **Train longer.** The speed and the flight phase keep improving past 150 million steps. A | ||
| billion steps takes under 2 hours on one L40S. |
There was a problem hiding this comment.
The page's own numbers are 150M steps in 19 minutes on one L40S, which is about 7.9M steps a minute. At that rate a billion steps takes about 127 minutes, more than 2 hours. Either give the measured time for a billion-step run, or say "about 2 hours".
| billion steps takes under 2 hours on one L40S. | |
| billion steps takes about 2 hours on one L40S. |
There was a problem hiding this comment.
Fixed: at the measured ~131K steps/s (150M in 19 min) a billion steps is about 2.1 h. The "keeps improving past 150M" claim is gone.
| `app.py` serves the champion as a FastAPI app. The champion's checkpoint is an app parameter | ||
| whose value is a `RunOutput`: when the app is deployed, Flyte resolves it to the output of the | ||
| latest successful `league` run, so a new league run followed by a redeploy serves the new | ||
| champion with no code change. The app scales to zero when nobody is playing. |
There was a problem hiding this comment.
Optional (coverage): app.py deploys the arena with requires_auth=False, so anyone with the URL can call it. The page doesn't say so. Add a sentence that the app is public by design, and how to keep it behind Union auth (drop requires_auth=False).
There was a problem hiding this comment.
The app no longer sets requires_auth=False, so it requires a tenant login by default. The page, README and a code comment say how to make it public. The page also says a later --smoke run's champion is what the app serves.
|
|
||
|  | ||
|
|
||
| ### What the noise taught us |
There was a problem hiding this comment.
.claude/rules/style.md ("Describe the current product. No history") rules out narrating earlier versions ("An earlier version trained…"). The heading also uses author "we" ("taught us"). Keep the actionable gotcha, "measure a single candidate's noise across seeds before trusting the ranking", and state it as advice. The same applies to the "An earlier version trained against the three most recent snapshots" paragraph in self-play-league/_index.md (line 169).
| ### What the noise taught us | |
| ### Measure the noise before you trust the ranking |
There was a problem hiding this comment.
Rewritten as present-tense advice under "Check the noise before you rank", with no history and no "we". The self-play "earlier version" paragraph is cut too.
| {{< code file="/unionai-examples/v2/tutorials/humanoid_locomotion/learn_to_run.py" fragment=run lang=python >}} | ||
|
|
||
| The first run builds the image, which takes a while because of the CUDA build of JAX. Later | ||
| runs reuse it. `--smoke` trains 20 million steps in a few minutes, which is enough to check |
There was a problem hiding this comment.
Optional (style): The example's docstring puts --smoke at about 8 minutes. Use that figure instead of "a few minutes", so readers know how long to wait.
| runs reuse it. `--smoke` trains 20 million steps in a few minutes, which is enough to check | |
| runs reuse it. `--smoke` trains 20 million steps in about 8 minutes, which is enough to check |
There was a problem hiding this comment.
Fixed: "about 8 minutes", matching the docstring. A fresh --smoke run trained in 7.8 min.
| # Reward design with Claude and sandboxes | ||
|
|
||
| > [!NOTE] | ||
| > Code available [on GitHub](https://github.com/unionai/unionai-examples/tree/main/v2/tutorials/eureka_reward_design). |
There was a problem hiding this comment.
Question: The unionai-examples pointer is pinned to 5e188bd, a commit from the open unionai-examples#348. Until #348 merges, the three "Code available on GitHub" links point at main paths that don't exist. If #348 is squash-merged, the pinned commit won't be on main. Will this PR wait for #348 and re-pin to its merge commit on main before it merges?
|
|
||
| ## Getting started | ||
|
|
||
| You need a Union cluster and an Anthropic API key stored as a secret. The code reads a secret |
There was a problem hiding this comment.
Question: The example pins unionai-sandbox==0.0.1b15, a pre-release, and uses the userns backend. Does that backend need user namespaces enabled on the cluster's nodes, or any other cluster setting? If so, the Getting started section should list it as a prerequisite next to "a Union cluster".
There was a problem hiding this comment.
From the unionai-sandbox 0.0.1b15 source: userns runs in an ordinary unprivileged pod, but the node must allow unprivileged user namespaces. If the cluster's seccomp profile blocks those syscalls, the pod needs CAP_SYS_ADMIN. It fails closed: if the backend can't start, run() raises rather than running the code unisolated. On kernels older than 5.13, Landlock isn't enforced, so the filesystem allow-list isn't either. The Getting started section and the sandbox section now list these requirements.
|
|
||
| `app.py` serves the champion as a FastAPI app. The champion's checkpoint is an app parameter | ||
| whose value is a `RunOutput`: when the app is deployed, Flyte resolves it to the output of the | ||
| latest successful `league` run, so a new league run followed by a redeploy serves the new |
There was a problem hiding this comment.
Question: The SDK docstring for flyte.app.RunOutput with task_name and no version says it resolves to "the latest output of an ephemeral task run". Is that guaranteed to be the latest successful run, as the page states, or only the latest run that has an output?
There was a problem hiding this comment.
Yes: in flyte 2.13.0, RunOutput(task_name=...) lists runs with in_phase=("succeeded",), newest first (src/flyte/app/_parameter.py).
…1949) * Move the SkyRL tutorial into Reinforcement learning (rename only) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> * Reinforcement learning: list the SkyRL tutorial in the section, for both variants Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> * Frontier AI tutorials: pretraining data refinery, checkpoint evals, video curation, sparse autoencoders, closed-loop discovery The section index now frames frontier AI by the workloads labs run day to day, with a table of who does each one and what makes it hard at scale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> * Bump unionai-examples to the Frontier AI tutorials and unionai-docs-infra to the SkyRL redirects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> --------- Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Review: the RL and Frontier AI tutorials on this branchPeeter asked me to review this branch before it goes to The tutorials are strong. Every Merge gates (in this order)
Across all pages
Variant leak (section indexes)No links leak into the flyte variant, but text that shows there advertises Union-only tutorials:
Split the card text with Reward design with Claude and sandboxes
Teach a humanoid to run
Self-play league for Connect Four
Curate a video dataset
Find features with sparse autoencoders
Refine a pretraining corpus
Evaluate every checkpoint
Closed-loop molecular discovery
Thanks for these. They're the most thorough tutorials in the section, and these are fixable. Happy to look again once they're in. Posted by Docsy at Peeter Piegaze (ppiegaze)'s direction. |
|
Docsy actions: what Docsy did on this PR, and at whose direction. Docsy adds a line for each.
|
There was a problem hiding this comment.
The three RL tutorials mostly match the example code they embed. The PR is bigger than its description says, and several pages break the no-history rule.
- Scope. The diff also carries all of #1949: five Frontier AI tutorials, the SkyRL move with its redirects, and the infra bump. The description doesn't mention any of it (see the PR description comment).
- Stale results. The sparse-autoencoders Results section publishes numbers from a run made before the published code's last changes.
- Eureka setup.
python eureka.pyimportsunionai-sandboxlocally, and the page never says to install it. - Eureka count. "130 policies" doesn't match the code's own arithmetic.
- History sections. "What we changed" / "taught us" sections appear on six pages.
- Variant card. The Flyte card names tutorials that are Union-only.
- Public app. The self-play app is public (
requires_auth=False) without saying so. - Questions. Sandbox pod privileges, and the examples pin.
Not verified: the Anthropic fallbacks beta, the measured results, the docs build, and whether the pinned examples commit is reachable from #348.
Questions for the author:
- The
unionai-examplessubmodule is pinned toce972d8, and I couldn't confirm it is on #348 (the checkout is shallow). Will it stay reachable after #348 merges, and is the bump to examplesmaintracked before this merges?
Other findings (on lines outside the diff, or past the limit of inline comments):
- On the description: The diff is bigger than the description says. Besides the three RL tutorials, it carries all of the #1949 commit: five Frontier AI tutorials (
checkpoint-evals,closed-loop-discovery,pretraining-data-refinery,sparse-autoencoders,video-data-curation) with their screenshots, a rewrite of the Frontier AI index, the move of the SkyRL tutorial fromfrontier-ai/toreinforcement-learning/, aunionai-docs-infrabump for its redirects, and 19 cspell words. That commit isn't onmainat the PR's base. Either rebase onto amainthat contains #1949, or describe that content here. The description also says the examples pointer moves to the commit in unionai-examples#348. The pinned commit,ce972d8, is titled "Sparse autoencoders tutorial: fixes from review", which suggests it comes from the Frontier AI examples work rather than #348.
What was checked (20 checks; 4 failed to run or not checked)
- ✗ Diff scope: git diff 52099ce fd08853, commit history, and whether #1949 is on origin/main (The head commit is #1949's content. It isn't on main at the base, and the PR description doesn't mention it.)
- ✓ Frontier AI pages: all 80 code shortcodes resolve to existing files and fragments in the pinned unionai-examples
- ✓ Frontier AI pages: prose numbers, commands, flags, and secrets against the example code (The checked values match the code.)
- ✓ Frontier AI pages: SDK claims (OnArtifact, Artifact.create/listall, flyte.Cache override and salt, new_condition, Condition.signal, CLI) against flyte-sdk main
- ✓ Frontier AI pages: relative links, images, alt text, variant gating
- ✗ Style rules (.claude/rules/style.md) on all eight new pages (History and time-relative passages on six pages.)
- ✓ Humanoid page prose against learn_to_run.py and g1.py
- ✓ flyte.artifacts Card.create_from, Metadata.create_model_metadata, produces_artifacts, flyte.remote.Artifact.get against flyte-sdk main
- ✗ Eureka page against eureka.py (The run count doesn't match, and the local install of unionai-sandbox is missing.)
- – Eureka Claude call (claude-opus-5-5, server-side fallback beta, output_config)
- ✗ userns sandbox backend privileges, against the generated flyte.PodTemplate reference (Raised as a question.)
- ✓ flyte create secret prompts for a value (flyte-sdk cli/_create.py)
- ✓ Self-play page against league.py and app.py, including the 231 matches / 92,400 games arithmetic
- ✓ flyte.app.RunOutput resolves to the latest succeeded run (flyte-sdk app/_parameter.py)
- ✗ Self-play app auth setting
- ✗ Variant gating of the RL section and the tutorials index card
- ✓ SkyRL move redirects in redirects.csv, and leftover links to the old path
- ⚠ Pinned unionai-examples commit is on #348 or on main (Shallow submodule with no remote refs.)
- – Docs build, markdownlint, cspell, link check
- – Measured results (speeds, Elo, success rates, run times)
Docsy, Union.ai's documentation agent. Advice only: it never approves or blocks.
| python find_features.py --widths 4096 16384 32768 | ||
| ``` | ||
|
|
||
| We ran this demo before the last changes to the autointerp code and the cache salts, so the |
There was a problem hiding this comment.
This says the numbers in this section come from a run made before the last changes to the autointerp code and the cache salts, so the published code didn't produce them. Lines 219-223 also mix timings from two runs. Rerun on the published code and report one run. Or cut the caveat and present the figures as illustrative. The "We ran" framing also breaks style.md's "Describe the current product" and "Product voice" rules.
There was a problem hiding this comment.
I reran the published code from an empty cache, and Results now reports that one run. The "earlier run" caveat and the "We ran" framing are gone. The 49 min wall clock is explained: 28 min of it was GPU queueing.
| `pythia-70m-topk-sae` artifact, with a card that shows its metrics and its five most | ||
| interpretable sampled features. | ||
|
|
||
| ### What the first run taught us |
There was a problem hiding this comment.
style.md, "Describe the current product": no history. The section "What the first run taught us" tells how the example developed. Keep the lesson as a present-tense gotcha and drop the story.
There was a problem hiding this comment.
Replaced with a present-tense gotcha, "Show Claude more than the strongest contexts".
| with the measured orbital energies, which arm found each molecule, and what the model predicted | ||
| before it was measured. Its card has the same charts and the top molecules. | ||
|
|
||
| ### What we changed along the way |
There was a problem hiding this comment.
style.md, "Describe the current product": no history ("previously", "we changed"). The heading "What we changed along the way" and the "now"s on lines 301 and 306 break it almost word for word. Rewrite as present-tense design notes, for example "Why the pool is neutral-only".
| ### What we changed along the way | |
| ### Design choices in the pool and the image |
There was a problem hiding this comment.
Rewritten as "Design choices in the pool and the oracle", in the present tense, with the "now"s gone. The drawing-library note moved to "The image and environments".
|
|
||
|  | ||
|
|
||
| ### What the first runs taught us |
There was a problem hiding this comment.
style.md, no history and no time-relative words. Examples include "What the first runs taught us", "The default is now 0.1" (line 302), "only as zip archives now" (lines 102-104), and "An earlier run … before the copy step" (lines 259-260). State the current defaults and behavior instead.
There was a problem hiding this comment.
Fixed: the section is now "Choosing the captioner, the motion threshold, and the timing", and the "now"s and the "earlier run" sentence are gone.
|
|
||
|  | ||
|
|
||
| ### What the iterations taught us |
There was a problem hiding this comment.
style.md, no history. "What the iterations taught us" frames a useful gotcha as a story. Retitle it, for example "Check the labeler before you distill", and write it in the present tense. Also, line 115 ("slice we checked") uses author "we", and lines 136-137 apologize for redundant code. Drop the question-only hash from the example instead.
| ### What the iterations taught us | |
| ### Check the labeler before you distill |
There was a problem hiding this comment.
Retitled "Check the labeler before you distill" and written in the present tense. "We checked" is gone. The redundant question-only hash is removed from the code, along with the apology.
| In training, where both sides sample their moves, its score against the tactical bot climbs | ||
| from 2% to 88%. | ||
|
|
||
| How the league picks opponents matters. An earlier version trained against the three most |
There was a problem hiding this comment.
style.md, no history. "An earlier version trained against the three most recent snapshots..." describes a past iteration. Rephrase it as a choice the reader makes: "Training against the three most recent snapshots instead of the strongest stalls at the tactical bot's level...".
There was a problem hiding this comment.
Cut. "Choosing opponents" now explains, in the present tense, why the league trains against the strongest snapshots instead of the most recent ones.
| {{< variant union >}} | ||
| {{< link-card target="reinforcement-learning" icon="trophy" title="Reinforcement Learning" >}} | ||
| RL fine-tuning of language models. | ||
| Reinforcement learning for LLMs, agents, robots, and games: GRPO, durable agentic RL, humanoid locomotion, model-written rewards, and self-play. |
There was a problem hiding this comment.
This PR removes the {{< variant union >}} wrapper, so this card now also shows in the Flyte variant. But GRPO, humanoid locomotion, model-written rewards, and self-play are all -flyte +union. Flyte readers find only the SkyRL tutorial. The section's own description frontmatter has the same problem. Gate the description by variant, or name only what both variants show.
There was a problem hiding this comment.
Fixed: the RL and Frontier AI descriptions (and their cards) are now variant-neutral. The Union-only intro text and comparison tables sit inside {{< variant union >}}.
| `app.py` serves the champion as a FastAPI app. The champion's checkpoint is an app parameter | ||
| whose value is a `RunOutput`: when the app is deployed, Flyte resolves it to the output of the | ||
| latest successful `league` run, so a new league run followed by a redeploy serves the new | ||
| champion with no code change. The app scales to zero when nobody is playing. |
There was a problem hiding this comment.
Optional (coverage): app.py sets requires_auth=False, so the arena has a public, unauthenticated URL on the reader's tenant. Say so, and say how to keep it private.
| champion with no code change. The app scales to zero when nobody is playing. | |
| champion with no code change. The app scales to zero when nobody is playing. It sets `requires_auth=False`, so anyone with its URL can play; remove that line to keep it private. |
There was a problem hiding this comment.
Same fix as above: login is required by default, and the page says how to opt into a public URL.
| class D,A cpu | ||
| ``` | ||
|
|
||
| ## What Flyte adds |
There was a problem hiding this comment.
Optional (style): This heading says "What Flyte adds" on a Union-only page that lists Union features. closed-loop-discovery uses "What Union adds".
| ## What Flyte adds | |
| ## What Union adds |
There was a problem hiding this comment.
Changed to "What Union.ai adds", here and on the Eureka and closed-loop pages.
|
|
||
| {{< code file="/unionai-examples/v2/tutorials/eureka_reward_design/harness.py" fragment=train lang=python >}} | ||
|
|
||
| Each training run gets its own sandbox session inside the task's pod. No pod is created, so a |
There was a problem hiding this comment.
Question: The candidates run in sb.on_device.session(backend="userns") inside eval_env, which sets no pod template. The generated flyte.PodTemplate reference says the userns backend needs CAP_SYS_ADMIN and an unconfined AppArmor profile on clusters whose seccomp/AppArmor defaults block the namespace syscalls. Does this tutorial work on every Union cluster as written, or only on clusters that allow unprivileged user namespaces?
There was a problem hiding this comment.
Answered on the thread above: it needs nodes that allow unprivileged user namespaces, or CAP_SYS_ADMIN where seccomp blocks them. It fails closed, and the page now says so. It also names bubblewrap with flyte.PodTemplate.allow_nested_sandboxing() as the stronger option.
Resolves the unionai-examples pointer conflict: both sides moved it (main to the ClusteredTaskEnvironment pretraining commit, this branch to the Frontier AI tutorials). The pointer now names the examples niels/rl-tutorials head, which merges both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com>
|
Addressed in 7dfd913, with the code changes in unionai/unionai-examples@5b861e8 on #348. Each inline thread has its own reply. Below is what changed for the items in the 2026-11-09 review comment. Merge gates
Across all pages
Reruns. These four tutorials were rerun on the fixed code. Every number and screenshot on their pages comes from the new runs.
Humanoid, video and closed-loop each got a Eureka
Humanoid
Self-play
Video
Sparse autoencoders
Refinery
Checkpoint evals
Closed-loop
Not changed:
🤖 Generated with Claude Code |
Adds three Reinforcement learning tutorials and five Frontier AI tutorials, and moves SkyRL into Reinforcement learning. The Frontier AI work came in through #1949, which merged into this branch. Every tutorial was validated end to end on a Union.ai cluster, and the screenshots come from those runs.
Reinforcement learning
humanoid-locomotion)eureka-reward-design)unionai-sandboxsession on a warm CPU pool, and the policies are graded outside the sandbox. Two hostile candidates are stoppedself-play-league)Frontier AI
OnArtifacttrigger runs a cached benchmark suite on each oneAlso in this PR
tutorials/frontier-ai/agentic-rl-skyrlmoves totutorials/reinforcement-learning/agentic-rl-skyrl. Its redirects are in Redirects for the SkyRL tutorial move into Reinforcement learning unionai-docs-infra#363. The.md-twin redirects follow in a separate infra PR.Pointers
unionai-docs-infrais at83d1850(docs-builder pr 223 #363 on inframain).unionai-examplesis at5b861e8on Reinforcement learning and Frontier AI tutorials unionai-examples#348's branch. Merge update streaming docs #348 first, then move the pointer to its merge commit onmain.Checks.
make distbuilds both variants. markdownlint, cspell,make check-links,check-imagesandcheck-subpage-cardspass.🤖 Generated with Claude Code