# Fabricium

Shared Hermes Plugin Infrastructure
Build Hermes plugins in 5 minutes — focus on your unique tools, not plugin boilerplate.

PyPI Python License

--- **Fabricium** is the shared foundation for all [Hermes Agent](https://hermes-agent.nousresearch.com) plugins. It extracts the ~500 lines of lifecycle boilerplate that every Hermes plugin duplicates — CLI commands (`setup`, `status`, `update`), bundled skill management, Git self-update, and state persistence — into a single `HermesPlugin` class. Plugin authors write one line to register everything and focus on what makes their plugin unique. Fabricium also provides a **Docker-based skill evaluation framework** (`fabricium.evals`) and a **Docker-based integration test environment** (`fabricium.testing`) so you can test and benchmark your plugins with real Hermes agent runs. --- ## Table of Contents - [Installation](#installation) - [Quick Start: Building a Plugin](#quick-start-building-a-plugin) - [What Fabricium Provides](#what-fabricium-provides) - [CLI Commands (setup / status / update)](#cli-commands-setup--status--update) - [Profile Modes](#profile-modes) - [State Persistence](#state-persistence) - [Bundled Skill Lifecycle](#bundled-skill-lifecycle) - [Git Self-Update](#git-self-update) - [Skill Evaluation Framework](#skill-evaluation-framework) - [Architecture](#evaluation-architecture) - [Quick Start: Running an Eval](#quick-start-running-an-eval) - [Defining Custom Tasks](#defining-custom-tasks) - [Defining Custom Rubrics](#defining-custom-rubrics) - [Judge Calibration](#judge-calibration) - [Integration Test Environment](#integration-test-environment) - [API Reference](#api-reference) - [Design Philosophy](#design-philosophy) - [Contributing](#contributing) - [License](#license) --- ## Installation ```bash pip install fabricium ``` Requires Python ≥ 3.10. Fabricium itself has **zero runtime dependencies** beyond the standard library. --- ## Quick Start: Building a Plugin Here's a complete Hermes plugin built with Fabricium. The unique business logic — a single `hello` tool — is just 3 lines: ```python # my_plugin/__init__.py from pathlib import Path from fabricium import HermesPlugin plugin = HermesPlugin( name="hello-world", plugin_dir=Path(__file__).parent, default_profile="default", ) def register(ctx): # One line registers setup/status/update CLI + bundled skills plugin.register(ctx) # Your unique tools — just business logic ctx.register_tool( name="hello", description="Say hello", handler=lambda args: print("Hello from my plugin!"), ) ``` That's it. Users get: ```bash $ hermes hello-world setup # Install skills, deploy SOUL.md $ hermes hello-world status # Check installation state $ hermes hello-world update # Git pull + refresh skills $ hermes hello-world update --check # Check for updates ``` Bundled skills in `my_plugin/skills/` are auto-discovered and registered. A `SOUL.md` in the plugin root can be deployed as the agent's identity. --- ## What Fabricium Provides ### CLI Commands (setup / status / update) | Command | Behaviour | |---|---| | `hermes setup` | Interactive: create profile, install bundled skills, optionally deploy SOUL.md | | `hermes status` | Display per-profile installation state (skills, SOUL.md, last updated) | | `hermes update` | Git fetch + fast-forward pull, detect/remove stale skills, refresh installed skills, sync profiles | | `hermes update --check` | Compare local vs remote HEAD, report ahead/behind counts | All commands are TTY-aware — in non-interactive contexts (CI, piped input), they use safe defaults without blocking. ### Profile Modes | Mode | `default_profile` | Behavior | |---|---|---| | **Single-profile** | `"my-profile"` | Setup installs directly to the named profile. Status shows one row. | | **Multi-profile** | `None` | Setup lists all available profiles for interactive selection. | ### State Persistence Installation state is stored as JSON under `~/.hermes/_state.json`. Each profile records: - Whether skills are installed - Whether SOUL.md was deployed - Last update timestamp The state file survives plugin updates and is used by `status` and `update` commands to track configuration across profiles. ### Bundled Skill Lifecycle Skills placed in the plugin's `skills/` directory are: - **Auto-discovered** — any subdirectory with a `SKILL.md` is registered - **Installed globally** — copied to `~/.hermes/skills/` so all profiles can load them - **Stale-detected** — on `update`, skills removed from the bundle are detected and offered for cleanup ### Git Self-Update The `update` command uses `git pull --ff-only` on the plugin's source directory. Before pulling: - Uncommitted changes are detected and the user is warned - Remote refs are fetched and ahead/behind counts are displayed - After pulling, stale skills are detected and removed, remaining skills are refreshed, and all profiles are synced All git operations are exposed in `fabricium.git_utils` for direct use — fetch, pull, diff, commit, ancestor checks, and more. --- ## Skill Evaluation Framework `fabricium.evals` provides a complete **LLM-as-Judge evaluation pipeline** for measuring how much a skill (like the Jovaltus pipeline) improves agent output quality. It follows the [SkillsBench](https://arxiv.org/abs/2503.12345) methodology: identical prompts, universal rubrics, and output-quality deltas between bare and skill-equipped profiles. ### Evaluation Architecture ``` ┌─────────────────────────────────────────────────┐ │ Eval Runner (CLI) │ │ python -m fabricium.evals.runner │ ├─────────────────────────────────────────────────┤ │ SkillEvalHarness │ │ ├── Docker container lifecycle │ │ ├── Multi-profile setup (bare vs skill) │ │ ├── Workspace init (git, seed files) │ │ ├── Agent execution with trace capture │ │ └── Result collection │ ├─────────────────────────────────────────────────┤ │ JudgeClient │ │ ├── Cross-provider judging (Anthropic/OpenAI) │ │ ├── Position randomization │ │ ├── Structured JSON parsing │ │ └── Calibration (Cohen's κ, Spearman ρ) │ └─────────────────────────────────────────────────┘ ``` ### Quick Start: Running an Eval ```bash # Set required env vars export EVAL_CANDIDATE_PROVIDER=deepseek export EVAL_CANDIDATE_MODEL=deepseek/deepseek-chat export EVAL_CANDIDATE_API_KEY=sk-... export EVAL_JUDGE_PROVIDER=anthropic export EVAL_JUDGE_MODEL=anthropic/claude-sonnet-4 export EVAL_JUDGE_API_KEY=sk-ant-... export EVAL_JOVALTUS_PLUGIN_DIR=/path/to/jovaltus/plugin # Run all tasks (3 runs per task per profile) python -m fabricium.evals.runner # Run a specific task EVAL_TASKS=python-backend python -m fabricium.evals.runner ``` Reports are written as JSON to `eval_results/` (configurable via `EVAL_OUTPUT_DIR`): ```json { "tasks": { "python-backend": { "runs": 12, "verdicts": [ {"profile": "bare", "dimensions": {...}, "total": 65.0}, {"profile": "jovaltus-agent", "dimensions": {...}, "total": 82.5} ] } }, "skill_lift": { "python-backend": { "bare_score": 65.0, "skill_score": 82.5, "skill_lift": 17.5 } } } ``` ### Defining Custom Tasks ```python from fabricium.evals import EvalTask task = EvalTask( id="my-task", name="My Custom Task", description="Tests whether the agent can build X", natural_prompt="Build a REST API with ...", # without skill keywords explicit_prompt="Using the pipeline, build ...", # with skill keywords seed_files={"pyproject.toml": "[project]\nname = ..."}, workspace_subdir="my-task", verify_commands=[ ("pytest", "cd /workspace/my-task && uv run pytest -v"), ], ) ``` ### Defining Custom Rubrics ```python from fabricium.evals import RubricSpec, RubricDimension, ScoringBand rubric = RubricSpec( task_id="my-task", dimensions=[ RubricDimension( id="functional_correctness", label="Functional Correctness", weight=0.40, description="Do all features work?", scoring_bands=[ ScoringBand(10, "All features work. All tests pass."), ScoringBand(7, "Most features work. One bug."), ScoringBand(5, "Half the features work."), ScoringBand(3, "Basic scaffolding only."), ScoringBand(0, "No implementation."), ], evidence_hints=["Run tests", "Check file tree"], ), ], ) ``` ### Judge Calibration ```python from fabricium.evals import calibrate human_labels = [ {"task_id": "my-task", "dimension": "functional_correctness", "score": 7}, {"task_id": "my-task", "dimension": "code_cleanliness", "score": 8}, ] metrics = calibrate(human_labels, judge_reports) # {"agreement_pct": 85.0, "cohens_kappa": 0.72, "spearman_rho": 0.88, "n_pairs": 50} ``` --- ## Integration Test Environment `fabricium.testing` provides a **Docker-based Hermes test environment** for integration testing plugins: ```python from pathlib import Path from fabricium.testing import HermesDockerTestEnv, CliAssert env = HermesDockerTestEnv( plugin_name="my-plugin", plugin_dir=Path(__file__).parent.parent, fabricium_src=Path("/path/to/fabricium"), ) with env: # Run CLI commands and assert output result = env.run_cli("my-plugin", "setup") assert result.success result = env.run_cli("my-plugin", "status") CliAssert(result).contains("Skills + SOUL.md ✓") ``` Key features: - **Disposable containers** — each test gets a clean Hermes environment - **TTY-safe** — non-interactive by default, suitable for CI - **Plugin auto-detection** — copies your plugin source into the container - **Fabricium mount** — mounts the fabricium source tree for editable installs --- ## API Reference ### `fabricium.HermesPlugin` ```python HermesPlugin( name: str, # Plugin name (CLI namespace, state file) plugin_dir: Path, # Plugin root directory default_profile: str | None = None, # None = multi-profile mode soul_md_path: str = "SOUL.md", # Relative to plugin_dir ) ``` **Key methods:** | Method | Description | |---|---| | `register(ctx)` | Register CLI commands + bundled skills with Hermes | ### `fabricium.evals` | Class | Description | |---|---| | `SkillEvalHarness` | Docker-based evaluation orchestrator | | `JudgeClient` | LLM-as-Judge with cross-provider support | | `EvalConfig` | Environment-variable-driven configuration | | `EvalTask` | Task definition (prompts, seed files, verify commands) | | `RubricSpec` / `RubricDimension` / `ScoringBand` | Rubric building blocks | | `JudgePrompt` | Customizable judge prompt template | | `calibrate()` | Judge-human agreement metrics | ### `fabricium.testing` | Class | Description | |---|---| | `HermesDockerTestEnv` | Docker-based Hermes test environment | | `CliAssert` | Composable CLI output assertions | | `HermesConfig` | Provider/model configuration for test env | ### `fabricium.git_utils` Git subprocess wrappers: `fetch_remote()`, `pull_branch()`, `get_ahead_behind()`, `get_diff()`, `get_diff_stat()`, `is_ancestor()`, `stage_all()`, `commit()`, and more. ### `fabricium.state` JSON state persistence: `load_state()`, `save_state()`, `set_profile_state()`. ### `fabricium.skills` Bundled skill lifecycle: `install_bundled_skills()`, `remove_stale_skills()`, `get_bundled_skill_names()`. ### `fabricium.prompts` TTY-aware prompts: `prompt_yes_no()`. --- ## Design Philosophy 1. **Extract only what's actually shared.** Fabricium contains code that both Caelterra and Jovaltus duplicated. No speculative abstractions for "future" needs. 2. **Convention over configuration.** Sensible defaults (single profile, standard paths). 99% of plugins need zero configuration. 3. **Library, not framework.** Plugins import Fabricium — Fabricium doesn't control plugins. Any behavior can be bypassed or replaced. 4. **Backward-compatible migration.** Plugins migrating to Fabricium keep the same CLI behavior, state file format, and user experience. --- ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and pull request guidelines. Quick start for development: ```bash git clone https://github.com/LaiTszKin/fabricium.git cd fabricium uv sync --dev uv run pytest # 104 tests uv run ruff check . # lint uv run mypy # type check ``` --- ## License MIT — see [LICENSE](LICENSE). ---

Fabricium — from Latin fabrica (workshop, forge). The foundation every Hermes plugin stands on.