From e41bdc1b37340510cf9a88d772479d00991df585 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 14:15:04 -0800 Subject: [PATCH 1/9] feat: create/update CLAUDE.md in install-skills for AI discoverability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After copying skill files, `art install-skills` now also creates or updates a CLAUDE.md at the project root with an ART skills section. This lets AI coding assistants (Claude Code, etc.) automatically discover `/train-sft` and `/train-rl` skills when entering a project. The update is idempotent—re-running the command won't duplicate content, and existing CLAUDE.md content is preserved. Co-Authored-By: Claude Opus 4.6 --- src/art/cli.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/art/cli.py b/src/art/cli.py index 62218dbd..2bca3035 100644 --- a/src/art/cli.py +++ b/src/art/cli.py @@ -13,6 +13,21 @@ SKILL_NAMES = ["train-sft", "train-rl"] +_ART_SECTION_START = "" +_ART_SECTION_END = "" +_ART_CLAUDE_MD_SECTION = f"""{_ART_SECTION_START} +## ART (Agent Reinforcement Trainer) + +This project uses [OpenPipe ART](https://art.openpipe.ai) for model training. + +### Available skills +- `/train-sft` - Create a supervised fine-tuning script +- `/train-rl` - Create a reinforcement learning training script + +Use these skills instead of writing training code from scratch. +{_ART_SECTION_END} +""" + WANDB_INFERENCE_BASE_URL = "https://api.inference.wandb.ai/v1" WANDB_INFERENCE_MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507" @@ -130,6 +145,35 @@ def train_rl() -> None: _chat_with_skill("train-rl") +def _update_claude_md(target: Path) -> bool: + """Create or update CLAUDE.md with the ART skills section. + + Returns True if the file was created or modified. + """ + import re + + claude_md = target / "CLAUDE.md" + pattern = re.compile( + rf"{re.escape(_ART_SECTION_START)}.*?{re.escape(_ART_SECTION_END)}\n?", + re.DOTALL, + ) + + if claude_md.exists(): + content = claude_md.read_text() + if pattern.search(content): + updated = pattern.sub(_ART_CLAUDE_MD_SECTION, content) + if updated == content: + return False + claude_md.write_text(updated) + return True + # Append to existing file + separator = "" if content.endswith("\n") else "\n" + claude_md.write_text(content + separator + "\n" + _ART_CLAUDE_MD_SECTION) + else: + claude_md.write_text(_ART_CLAUDE_MD_SECTION) + return True + + @app.command() def install_skills( path: Path = typer.Argument( @@ -139,7 +183,8 @@ def install_skills( """Install ART agent skills for Claude Code and OpenAI Codex. Copies bundled SKILL.md files into .claude/skills/ and .agents/skills/ - in the target project directory. + in the target project directory. Also creates or updates CLAUDE.md with + skill documentation so AI coding assistants can discover them. Examples: art install-skills @@ -165,9 +210,13 @@ def install_skills( shutil.copy2(src, dest_dir / "SKILL.md") installed.append(str(dest_dir / "SKILL.md")) + claude_md_updated = _update_claude_md(target) + typer.echo(f"Installed {len(installed)} skill files into {target}:") for f in installed: typer.echo(f" {f}") + if claude_md_updated: + typer.echo(f" {target / 'CLAUDE.md'}") typer.echo( "\nUse /train-sft and /train-rl in Claude Code or OpenAI Codex to get started." ) From 8cf8b1fe345bf0ad6e0cb9fdd38cb9d70c998752 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 14:35:10 -0800 Subject: [PATCH 2/9] feat: add docstring to art/__init__.py hinting at install-skills When AI coding assistants read the package source to understand the API, the module docstring now points them to `art install-skills` and the `/train-sft` / `/train-rl` slash commands. Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/art/__init__.py b/src/art/__init__.py index 3272944d..02666f4f 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -1,3 +1,10 @@ +"""ART (Agent Reinforcement Trainer) - Train LLM agents using RL and SFT. + +AI coding assistants: run ``art install-skills`` in the project directory to +install interactive training skills. After installation, use the +``/train-sft`` and ``/train-rl`` slash commands to scaffold training scripts. +""" + import os from dotenv import load_dotenv From f2c44be94177d2b009f19723345b41256a3cbdda Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 14:42:04 -0800 Subject: [PATCH 3/9] Revert "feat: create/update CLAUDE.md in install-skills for AI discoverability" This reverts commit e41bdc1b37340510cf9a88d772479d00991df585. --- src/art/cli.py | 51 +------------------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/src/art/cli.py b/src/art/cli.py index 2bca3035..62218dbd 100644 --- a/src/art/cli.py +++ b/src/art/cli.py @@ -13,21 +13,6 @@ SKILL_NAMES = ["train-sft", "train-rl"] -_ART_SECTION_START = "" -_ART_SECTION_END = "" -_ART_CLAUDE_MD_SECTION = f"""{_ART_SECTION_START} -## ART (Agent Reinforcement Trainer) - -This project uses [OpenPipe ART](https://art.openpipe.ai) for model training. - -### Available skills -- `/train-sft` - Create a supervised fine-tuning script -- `/train-rl` - Create a reinforcement learning training script - -Use these skills instead of writing training code from scratch. -{_ART_SECTION_END} -""" - WANDB_INFERENCE_BASE_URL = "https://api.inference.wandb.ai/v1" WANDB_INFERENCE_MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507" @@ -145,35 +130,6 @@ def train_rl() -> None: _chat_with_skill("train-rl") -def _update_claude_md(target: Path) -> bool: - """Create or update CLAUDE.md with the ART skills section. - - Returns True if the file was created or modified. - """ - import re - - claude_md = target / "CLAUDE.md" - pattern = re.compile( - rf"{re.escape(_ART_SECTION_START)}.*?{re.escape(_ART_SECTION_END)}\n?", - re.DOTALL, - ) - - if claude_md.exists(): - content = claude_md.read_text() - if pattern.search(content): - updated = pattern.sub(_ART_CLAUDE_MD_SECTION, content) - if updated == content: - return False - claude_md.write_text(updated) - return True - # Append to existing file - separator = "" if content.endswith("\n") else "\n" - claude_md.write_text(content + separator + "\n" + _ART_CLAUDE_MD_SECTION) - else: - claude_md.write_text(_ART_CLAUDE_MD_SECTION) - return True - - @app.command() def install_skills( path: Path = typer.Argument( @@ -183,8 +139,7 @@ def install_skills( """Install ART agent skills for Claude Code and OpenAI Codex. Copies bundled SKILL.md files into .claude/skills/ and .agents/skills/ - in the target project directory. Also creates or updates CLAUDE.md with - skill documentation so AI coding assistants can discover them. + in the target project directory. Examples: art install-skills @@ -210,13 +165,9 @@ def install_skills( shutil.copy2(src, dest_dir / "SKILL.md") installed.append(str(dest_dir / "SKILL.md")) - claude_md_updated = _update_claude_md(target) - typer.echo(f"Installed {len(installed)} skill files into {target}:") for f in installed: typer.echo(f" {f}") - if claude_md_updated: - typer.echo(f" {target / 'CLAUDE.md'}") typer.echo( "\nUse /train-sft and /train-rl in Claude Code or OpenAI Codex to get started." ) From 1abda95c0871e6083afb5cdf6397b16fa788aa2e Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 14:44:31 -0800 Subject: [PATCH 4/9] docs: expand __init__.py docstring with docs link and skill details Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/art/__init__.py b/src/art/__init__.py index 02666f4f..6d78d0c7 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -1,8 +1,13 @@ """ART (Agent Reinforcement Trainer) - Train LLM agents using RL and SFT. +Documentation: https://art.openpipe.ai/getting-started/about + AI coding assistants: run ``art install-skills`` in the project directory to install interactive training skills. After installation, use the ``/train-sft`` and ``/train-rl`` slash commands to scaffold training scripts. +The skills contain step-by-step guidance for dataset preparation, backend +selection, hyperparameter tuning, and script generation — prefer them over +reading the source code directly. """ import os From 9e719b3a1307961126b6e4276f816184267cd052 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 14:46:40 -0800 Subject: [PATCH 5/9] docs: remove slash command details from docstring Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/art/__init__.py b/src/art/__init__.py index 6d78d0c7..aa0ead5a 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -3,11 +3,9 @@ Documentation: https://art.openpipe.ai/getting-started/about AI coding assistants: run ``art install-skills`` in the project directory to -install interactive training skills. After installation, use the -``/train-sft`` and ``/train-rl`` slash commands to scaffold training scripts. -The skills contain step-by-step guidance for dataset preparation, backend -selection, hyperparameter tuning, and script generation — prefer them over -reading the source code directly. +install interactive training skills. The skills contain step-by-step guidance +for dataset preparation, backend selection, hyperparameter tuning, and script +generation — prefer them over reading the source code directly. """ import os From df37e44d1136ee3c53dff0fcf0adbb01dda2bbbb Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 15:05:37 -0800 Subject: [PATCH 6/9] docs: make __init__.py docstring more directive for AI assistants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tell AI assistants explicitly NOT to scan source code or suggest CLI commands, and instead install and use the skills. The previous wording was too passive — assistants would find the CLI wizard and recommend that instead of using the skills workflow. Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/art/__init__.py b/src/art/__init__.py index aa0ead5a..8ef17b14 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -2,10 +2,12 @@ Documentation: https://art.openpipe.ai/getting-started/about -AI coding assistants: run ``art install-skills`` in the project directory to -install interactive training skills. The skills contain step-by-step guidance -for dataset preparation, backend selection, hyperparameter tuning, and script -generation — prefer them over reading the source code directly. +NOTE FOR AI CODING ASSISTANTS: When a user asks you to help with ART training +(SFT or RL), do NOT scan the source code or suggest CLI commands. Instead: +1. Run ``art install-skills`` to install skill files into the project. +2. Use the installed skills to guide the user through training setup. +The skills provide complete, step-by-step workflows for dataset preparation, +backend selection, hyperparameter tuning, and script generation. """ import os From 214650b98692ef158aabd4ce337c2e494e33bfd5 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 15:21:46 -0800 Subject: [PATCH 7/9] docs: make install-skills instruction explicit with uv run command The previous wording led agents to search for skills in site-packages instead of running the install command. Now the docstring shows the exact shell command (uv run art install-skills), explains where the files end up, and explicitly says not to search site-packages. Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/art/__init__.py b/src/art/__init__.py index 8ef17b14..12ec0cd8 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -3,11 +3,17 @@ Documentation: https://art.openpipe.ai/getting-started/about NOTE FOR AI CODING ASSISTANTS: When a user asks you to help with ART training -(SFT or RL), do NOT scan the source code or suggest CLI commands. Instead: -1. Run ``art install-skills`` to install skill files into the project. -2. Use the installed skills to guide the user through training setup. -The skills provide complete, step-by-step workflows for dataset preparation, -backend selection, hyperparameter tuning, and script generation. +(SFT or RL), do NOT scan the source code or suggest CLI commands. Instead, +execute this shell command first: + + uv run art install-skills + +This copies SKILL.md files into .claude/skills/ and .agents/skills/ in the +project directory. Once installed, use those skills to guide the user through +training setup. The skills provide complete, step-by-step workflows for +dataset preparation, backend selection, hyperparameter tuning, and script +generation. Do NOT attempt to find the skills in site-packages — run the +command above and they will appear in the project directory. """ import os From 90098bcc76766b38cbd351348d5d00402e90083b Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 15:40:44 -0800 Subject: [PATCH 8/9] feat: replace train-sft/train-rl with init and help commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove `art train-sft` and `art train-rl` CLI wizards — users interact with ART through AI coding assistants, not standalone CLI wizards. Add `art init` to initialize a project with skill files (replaces `install-skills` which is kept as an alias). Add `art help` that explains the setup flow for agents. Update __init__.py docstring to reference `art init`. Co-Authored-By: Claude Opus 4.6 --- src/art/__init__.py | 2 +- src/art/cli.py | 167 ++++++++++++++------------------------------ 2 files changed, 54 insertions(+), 115 deletions(-) diff --git a/src/art/__init__.py b/src/art/__init__.py index 12ec0cd8..8e494e6c 100644 --- a/src/art/__init__.py +++ b/src/art/__init__.py @@ -6,7 +6,7 @@ (SFT or RL), do NOT scan the source code or suggest CLI commands. Instead, execute this shell command first: - uv run art install-skills + uv run art init This copies SKILL.md files into .claude/skills/ and .agents/skills/ in the project directory. Once installed, use those skills to guide the user through diff --git a/src/art/cli.py b/src/art/cli.py index 62218dbd..9758db1a 100644 --- a/src/art/cli.py +++ b/src/art/cli.py @@ -13,9 +13,6 @@ SKILL_NAMES = ["train-sft", "train-rl"] -WANDB_INFERENCE_BASE_URL = "https://api.inference.wandb.ai/v1" -WANDB_INFERENCE_MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507" - def _get_skill_path(skill_name: str) -> Path: """Find a skill file, checking installed package first, then repo root.""" @@ -36,118 +33,10 @@ def _get_skill_path(skill_name: str) -> Path: raise FileNotFoundError(f"Skill '{skill_name}' not found") -def _chat_with_skill(skill_name: str) -> None: - """Run an interactive chat session using a skill as the system prompt.""" - import os - import sys - - from openai import OpenAI - - api_key = os.environ.get("WANDB_API_KEY") - if not api_key: - typer.echo( - "Error: WANDB_API_KEY environment variable is required.\n" - "Get your key at https://wandb.ai/authorize", - err=True, - ) - raise typer.Exit(1) - - try: - skill_path = _get_skill_path(skill_name) - except FileNotFoundError: - typer.echo( - f"Error: Skill '{skill_name}' not found in this installation.", err=True - ) - raise typer.Exit(1) - - skill_content = skill_path.read_text() - # Strip YAML frontmatter - if skill_content.startswith("---"): - end = skill_content.find("---", 3) - if end != -1: - skill_content = skill_content[end + 3 :].strip() - - cli_preamble = ( - "IMPORTANT: You are in a plain text chat. You have NO tools. " - "You cannot search files, read files, run scripts, validate data, or execute code. " - "Never say 'Let me search...', 'Scanning...', 'Found N files...', or 'Valid! N rows' — " - "you did not do any of these things. " - "If a step requires running code or using tools, skip it entirely and move on.\n\n" - ) - - client = OpenAI(base_url=WANDB_INFERENCE_BASE_URL, api_key=api_key) - messages: list = [{"role": "system", "content": cli_preamble + skill_content}] - - typer.echo(f"ART {skill_name} wizard (powered by {WANDB_INFERENCE_MODEL})") - typer.echo("Type 'quit' to exit.\n") - - # Send an initial empty user message to kick off the wizard - messages.append({"role": "user", "content": "Hi, let's get started."}) - - while True: - try: - stream = client.chat.completions.create( - model=WANDB_INFERENCE_MODEL, - messages=messages, - stream=True, - ) - assistant_message = "" - for chunk in stream: - if not chunk.choices: - continue - delta = chunk.choices[0].delta.content - if delta: - sys.stdout.write(delta) - sys.stdout.flush() - assistant_message += delta - typer.echo("") # newline after streaming - messages.append({"role": "assistant", "content": assistant_message}) - except Exception as e: - typer.echo(f"\nError from inference API: {e}", err=True) - raise typer.Exit(1) - - try: - user_input = typer.prompt("\nYou") - except (KeyboardInterrupt, EOFError): - typer.echo("\nExiting.") - break - - if user_input.strip().lower() == "quit": - break - - messages.append({"role": "user", "content": user_input}) - - -@app.command() -def train_sft() -> None: - """Interactive wizard to create an SFT training script.""" - _chat_with_skill("train-sft") - - -@app.command() -def train_rl() -> None: - """Interactive wizard to create an RL training script.""" - _chat_with_skill("train-rl") - - -@app.command() -def install_skills( - path: Path = typer.Argument( - default=Path("."), help="Project directory to install skills into" - ), -) -> None: - """Install ART agent skills for Claude Code and OpenAI Codex. - - Copies bundled SKILL.md files into .claude/skills/ and .agents/skills/ - in the target project directory. - - Examples: - art install-skills - art install-skills /path/to/my-project - """ +def _install_skills(target: Path) -> list[str]: + """Copy bundled SKILL.md files into .claude/skills/ and .agents/skills/.""" import shutil - target = path.resolve() destinations = [ target / ".claude" / "skills", target / ".agents" / "skills", @@ -164,8 +53,28 @@ def install_skills( dest_dir.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dest_dir / "SKILL.md") installed.append(str(dest_dir / "SKILL.md")) + return installed + + +@app.command() +def init( + path: Path = typer.Argument( + default=Path("."), help="Project directory to initialize" + ), +) -> None: + """Initialize ART in a project directory. + + Installs agent skill files into .claude/skills/ and .agents/skills/ + so that AI coding assistants can use them. + + Examples: + art init + art init /path/to/my-project + """ + target = path.resolve() + installed = _install_skills(target) - typer.echo(f"Installed {len(installed)} skill files into {target}:") + typer.echo(f"Initialized ART in {target}:") for f in installed: typer.echo(f" {f}") typer.echo( @@ -173,6 +82,36 @@ def install_skills( ) +@app.command() +def install_skills( + path: Path = typer.Argument( + default=Path("."), help="Project directory to install skills into" + ), +) -> None: + """Install ART agent skills (alias for 'art init').""" + init(path) + + +@app.command(name="help") +def help_command() -> None: + """Show how to get started with ART using AI coding assistants.""" + typer.echo( + "ART (Agent Reinforcement Trainer)\n" + "https://art.openpipe.ai/getting-started/about\n" + "\n" + "To set up ART in your project, run:\n" + "\n" + " uv run art init\n" + "\n" + "This installs skill files into .claude/skills/ and .agents/skills/\n" + "that teach AI coding assistants how to create training scripts.\n" + "\n" + "After initialization, use these skills in your AI coding assistant:\n" + " /train-sft - Create a supervised fine-tuning script\n" + " /train-rl - Create a reinforcement learning training script\n" + ) + + @app.command() def migrate( path: Path = typer.Argument( From 438a062b354e41a8b92343b8526864ec526b2769 Mon Sep 17 00:00:00 2001 From: Bohdan Date: Thu, 19 Feb 2026 15:42:39 -0800 Subject: [PATCH 9/9] refactor: install-skills owns the logic, init delegates to it Keep install-skills as the primary command with full docstring and output. init calls install-skills and can be extended later. Co-Authored-By: Claude Opus 4.6 --- src/art/cli.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/art/cli.py b/src/art/cli.py index 9758db1a..44c27830 100644 --- a/src/art/cli.py +++ b/src/art/cli.py @@ -57,24 +57,24 @@ def _install_skills(target: Path) -> list[str]: @app.command() -def init( +def install_skills( path: Path = typer.Argument( - default=Path("."), help="Project directory to initialize" + default=Path("."), help="Project directory to install skills into" ), ) -> None: - """Initialize ART in a project directory. + """Install ART agent skills for Claude Code and OpenAI Codex. - Installs agent skill files into .claude/skills/ and .agents/skills/ - so that AI coding assistants can use them. + Copies bundled SKILL.md files into .claude/skills/ and .agents/skills/ + in the target project directory. Examples: - art init - art init /path/to/my-project + art install-skills + art install-skills /path/to/my-project """ target = path.resolve() installed = _install_skills(target) - typer.echo(f"Initialized ART in {target}:") + typer.echo(f"Installed {len(installed)} skill files into {target}:") for f in installed: typer.echo(f" {f}") typer.echo( @@ -83,13 +83,18 @@ def init( @app.command() -def install_skills( +def init( path: Path = typer.Argument( - default=Path("."), help="Project directory to install skills into" + default=Path("."), help="Project directory to initialize" ), ) -> None: - """Install ART agent skills (alias for 'art init').""" - init(path) + """Initialize ART in a project directory. + + Examples: + art init + art init /path/to/my-project + """ + install_skills(path) @app.command(name="help")