Skip to content

feat(integrations): external plugin sources (git/local) in plugins:#44

Open
thad0ctor wants to merge 6 commits into
mainfrom
feat/external-plugin-sources
Open

feat(integrations): external plugin sources (git/local) in plugins:#44
thad0ctor wants to merge 6 commits into
mainfrom
feat/external-plugin-sources

Conversation

@thad0ctor

@thad0ctor thad0ctor commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the ability to load a plugin straight from an external git repo or local directory declared in the config, without modifying the Axolotl checkout or install. Each plugins: entry may now be either a dotted class path (unchanged) or a mapping describing where the plugin lives.

plugins:
  - axolotl.integrations.liger.LigerPlugin          # str form — unchanged
  - cls: my_plugin.MyPlugin                          # external git source
    source: https://github.com/org/repo.git
    ref: v1.2.0                                       # branch/tag/commit (SHA recommended)
    subdir: src                                       # dir within source on sys.path
    pip_install: editable                             # false | editable | requirements
  - cls: local_plugin.Thing                           # local dir (no clone)
    source: ./plugins/local_plugin

Axolotl clones/resolves the source into a self-ignoring cache dir (./.axolotl_plugins/ by default, override via plugin_cache_dir: / AXOLOTL_PLUGIN_CACHE_DIR), adds it to sys.path, optionally installs the plugin + its deps, then loads it by dotted class path.

Design

  • PluginSpec schema: cls / source / ref / subdir / pip_install / update; new top-level plugin_cache_dir. Field widened to list[str | PluginSpec].
  • provisioning.py: git clone/update, local resolve, filelock-guarded so multi-process / multi-GPU launches sharing one cache don't race; pip_install supports editable (pip install -e, falling back to requirements if not a package) and requirements (pip install -r).
  • Normalize early: provision_plugins() rewrites cfg["plugins"] to a flat list[str] before validation, so every downstream consumer (the registration loops, spectrum/args.py's substring check, the builders) is untouched. Backward compatible.
  • Cache dir self-ignores; .axolotl_plugins/ also added to repo .gitignore. filelock declared in pyproject.toml (now a direct import).
  • pip_install only ever touches the plugin + its deps — never the Axolotl install.

Docs

  • docs/custom_integrations.qmd: new "External plugin sources" section (field table, cache dir, security note).
  • docs/agents/plugins.md: new agent reference + registered plugins topic (axolotl agent-docs plugins) + AGENTS.md entry.
  • config-reference.qmd is auto-generated from the schema and picks up the new fields.

Tests

tests/integrations/test_plugin_provisioning.py — 14 cases: str-noop, local source, subdir, mixed entries, missing path, cache self-ignore, env override, a real local git clone-and-load + idempotent reuse, and pip_install dispatch (mocked).

Manual verification

Validated end-to-end against a real example plugin (config args + a TrainerCallback) cloned from a live GitHub repo:
https://github.com/thad0ctor/axolotl-example-plugin — reviewers can point a config at it to exercise this.

Note: the example repo above is a personal repo used for review/testing; if merged upstream it could be re-homed under the axolotl-ai-cloud org.

Scope note

This loads self-contained plugins that hook in only through the public BasePlugin API. Plugins that require edits to Axolotl core are out of scope by design.

Summary by CodeRabbit

  • New Features

    • Support for external plugins from git repositories and local directories
    • Configurable plugin cache directory with environment variable override
  • Documentation

    • Added comprehensive plugin documentation and external plugin sources guide
  • Tests

    • Added plugin provisioning integration tests
  • Chores

    • Added filelock dependency

Allow each `plugins:` entry to be either a dotted class path (unchanged)
or a mapping that points at an external git repo or local directory.
Axolotl clones/resolves the source into a self-ignoring cache dir, adds
it to sys.path, optionally installs the plugin + its deps, then loads it
by dotted class path — leaving the Axolotl checkout and install untouched.

- PluginSpec schema (cls/source/ref/subdir/pip_install/update) + plugin_cache_dir
- provisioning.py: git clone/update, local resolve, filelock-guarded for
  multi-process launches, pip editable/requirements install modes
- normalize plugins to list[str] before validation so no downstream
  consumer changes (registration loops, spectrum args, builders)
- cache dir self-ignores; .axolotl_plugins/ added to repo .gitignore
- docs: custom_integrations "External plugin sources" section + agent doc
- tests: 14 cases incl. a real local git-clone-and-load
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Worried about impact? Review this PR in Change Stack to explore blast radius before you approve or request changes.

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5976c32b-b1a4-4669-9d0f-4e9ac30ca163

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Add external plugin source support to Axolotl training configs: users specify plugins via simple dotted class paths or structured specs with git/local sources, optional installation modes, and subdirectory selection. The implementation includes git cloning with file-locked caching, pip install dispatch, and normalized config resolution; comprehensive documentation and test suite accompany the feature.

Changes

External Plugin Provisioning Feature

Layer / File(s) Summary
Plugin Schema and Config Contract
src/axolotl/utils/schemas/config.py
Define PluginSpec model with required cls field and optional source, ref, subdir, pip_install, and update fields; extend AxolotlInputConfig.plugins to accept string or PluginSpec entries and add plugin_cache_dir config option.
Plugin Provisioning Core Logic
src/axolotl/integrations/provisioning.py
Implement provision_plugins(cfg) function and helpers for cache management, git source detection, subprocess git clone/fetch/checkout with FileLock serialization, local source resolution with file:// support, pip installation in editable/requirements modes, and sys.path injection for non-editable plugins.
Config Pipeline Integration
src/axolotl/cli/config.py
Import provision_plugins and delegate external plugin provisioning in prepare_plugins(cfg) during config loading.
Documentation, Dependencies, and Setup
pyproject.toml, .gitignore, src/axolotl/cli/agent_docs/__init__.py, AGENTS.md, docs/agents/plugins.md, docs/custom_integrations.qmd
Add filelock dependency, register .axolotl_plugins/ cache directory in .gitignore, register agent documentation topic, provide comprehensive plugin configuration reference and implementation guidance, and document cache behavior and security considerations.
Comprehensive Test Coverage
tests/integrations/test_plugin_provisioning.py
Test helpers, fixtures, and assertions covering git detection, plugin spec validation, no-op handling, local and git source provisioning with subdirectory support, error handling, mixed configs, cache directory behavior, environment override, real git repo integration, and pip installation dispatch in editable/requirements modes.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: external plugin sources (git/local) support in the plugins config section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/external-plugin-sources

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integrations/test_plugin_provisioning.py`:
- Around line 17-20: The call to subprocess.run in the helper function _git uses
a partial executable "git" which triggers security lint S607; change _git to
resolve the absolute git binary with shutil.which("git") and pass that absolute
path as the first element of the command list to subprocess.run, and if
shutil.which returns None raise a clear RuntimeError (or pytest.skip) so tests
fail fast rather than invoking a partial path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3af7fdb6-2fb3-4354-ab4d-f84c5305aff7

📥 Commits

Reviewing files that changed from the base of the PR and between 69f6d8b and 04acd8a.

📒 Files selected for processing (10)
  • .gitignore
  • AGENTS.md
  • docs/agents/plugins.md
  • docs/custom_integrations.qmd
  • pyproject.toml
  • src/axolotl/cli/agent_docs/__init__.py
  • src/axolotl/cli/config.py
  • src/axolotl/integrations/provisioning.py
  • src/axolotl/utils/schemas/config.py
  • tests/integrations/test_plugin_provisioning.py

Comment thread tests/integrations/test_plugin_provisioning.py
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

📖 Documentation Preview:

Deployed on Netlify from commit 079f8a8

thad0ctor added 5 commits June 5, 2026 13:09
Use shutil.which to pass an absolute git executable to subprocess.run in
the test helper, skipping the test if git is unavailable. Addresses
CodeRabbit review on PR #44.
…dir traversal

Addresses agent review of PR #44:
- axolotl.utils.config.prepare_plugins (public, used by tests + docs) now
  also calls provision_plugins, so external/dict plugin entries work through
  that entry point and not only axolotl.cli.config. Previously a dict entry
  reaching it crashed in PluginManager.register (dict.rsplit).
- clamp PluginSpec.subdir to within the source root; reject '..'/absolute
  subdir that would inject an arbitrary directory onto sys.path.
- tests: utils.config provisioning path, relative + absolute subdir escape.
… clone

Second-round agent review:
- git fetch was followed by a no-op checkout, so update: true never advanced
  an already-checked-out branch (default branch or branch ref). Fast-forward
  to the fetched remote tip after fetching.
- only attempt the fast-forward when HEAD is on a branch; a tag/SHA ref leaves
  HEAD detached and immutable, so there is nothing to advance (avoids a
  spurious, swallowed 'not on a branch' error).
- tests: update advances the default branch; update with a tag ref stays pinned
  and does not raise.
cls is now optional. When a source is given without cls, Axolotl imports the
package(s) under the source (or its subdir) and uses the single BasePlugin
subclass found, so a config can be just:

    plugins:
      - source: https://github.com/org/my-plugin.git
        ref: v1.0.0

Convention for discoverable plugins: an importable package whose top-level
__init__ exports exactly one BasePlugin subclass. Discovery skips tests/, docs/,
examples/, build/, dist/; zero or multiple matches raise and ask for an explicit
cls (which remains the override). A PluginSpec now requires at least one of
cls/source.

- tests: discovery (root + subdir), multiple/zero-match errors, tests-dir skip,
  cls-or-source validation
- docs: convention + discovery in custom_integrations.qmd and agent plugins doc
cls now accepts a list, so several plugin classes can be loaded from a single
source (cloned once) without repeating source/ref:

    plugins:
      - source: https://github.com/org/multi-plugin.git
        ref: v1.0.0
        cls: [multi_pkg.AlphaPlugin, multi_pkg.BetaPlugin]

String -> one plugin; list -> several; empty list/omitted -> discover the one.

- tests: cls list (alone + mixed with string entries), empty-list falls back to
  discovery, validation accepts a list
- docs: multi-plugin example + cls field note in both docs
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