Extension Guide
This document describes how to extend ARI for new use cases, domains, and capabilities. ARI is designed for zero-core-code changes when adding new experiments, skills, or pipeline stages.
1. Adding a New Experiment Domain
The most common extension. Requires no code changes.
Steps
- Write
your_experiment.md:
# Protein Folding Optimization
## Research Goal
Minimize energy score of protein folding simulation using different force field parameters.
## Required Workflow
1. Call `survey` to find related literature
2. Submit a SLURM job with `slurm_submit`
3. Poll until completion with `job_status`
4. Read results with `run_bash`
<!-- min_expected_metric: -500 -->
<!-- metric_keyword: energy_score -->- Run:
ari run your_experiment.md --config config/bfts.yamlThat's it. ARI reads the goal, proposes hypotheses, and searches autonomously.
Domain Customization via experiment.md
| Section | Purpose | Impact |
|---|---|---|
## Research Goal | What to optimize | Drives LLM hypothesis generation |
## Required Workflow | Which tools, in what order | Sets tool_sequence in WorkflowHints |
## Hardware Limits | Hard constraints | Injected into every agent step as system hint |
## SLURM Script Template | Starting point for experiments | LLM modifies this for each hypothesis |
<!-- metric_keyword: X --> | What metric to extract | Used by evaluator and evaluator-skill |
<!-- min_expected_metric: N --> | Minimum acceptable value | Triggers validation check |
2. Adding a New MCP Skill
Add capabilities (new tools) to the agent without touching ari-core.
Skill Structure
ari-skill-yourskill/
├── src/
│ └── server.py ← FastMCP server (required)
├── tests/
│ └── test_server.py ← Tests (minimum 3)
├── pyproject.toml ← Package config
├── README.md ← Tool descriptions and examples
└── REQUIREMENTS.md ← Design specServer Template
# src/server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("your-skill")
@mcp.tool()
def your_tool(param: str, option: int = 10) -> dict:
"""
Clear description that appears in the LLM's tool list.
Args:
param: What this parameter does
option: What this option controls (default: 10)
Returns:
result: The computed output
"""
# RULE: No LLM calls here. Pure function.
processed = pure_computation(param, option)
return {"result": processed}
if __name__ == "__main__":
mcp.run()Registration
In your BFTS config YAML:
skills:
- name: your-skill
path: /abs/path/to/ari-skill-yourskillIn your experiment.md:
## Required Workflow
1. Call `your_tool` with the experiment parametersSkill Design Checklist
- [ ] No LLM calls inside tool functions (P2)
- [ ] Returns a
dictwith clear keys - [ ] Tool docstring clearly explains inputs, outputs, and side effects
- [ ] At least 3 tests covering normal, edge, and error cases
- [ ] README.md with usage examples
- [ ] REQUIREMENTS.md with design spec
3. Adding a Post-BFTS Pipeline Stage
Add automated post-processing after the BFTS search completes. Only edit config/pipeline.yaml. No core code changes needed.
pipeline:
- stage: generate_paper
skill: ari-skill-paper
tool: generate_section
enabled: true
args:
venue: arxiv
- stage: review
skill: ari-skill-paper
tool: review_section
enabled: true
- stage: my_new_stage # ← Add here
skill: ari-skill-yourskill
tool: your_analysis_tool
enabled: true
args:
custom_param: value
- stage: reproducibility_check
skill: ari-skill-paper-re
tool: reproducibility_report
enabled: trueEach stage receives:
best_node: The highest-scoring node from BFTSall_nodes: All explored nodesnodes_json_path: Path tonodes_tree.json- Any
argsspecified in the YAML
4. Supporting a New LLM Backend
Supported via litellm. In most cases, only the config changes.
# OpenAI
llm:
backend: openai
model: gpt-4o
# Anthropic
llm:
backend: anthropic
model: claude-sonnet-4-5
# Any OpenAI-compatible API (vLLM, LM Studio, etc.)
llm:
backend: openai
model: your-model-name
base_url: http://your-server:8000/v1If the LLM does not support function/tool calling, set tool_choice="none" in config/bfts.yaml and ensure the experiment workflow uses ## Required Workflow to guide step-by-step execution.
5. Adding a New Venue for Paper Generation
Paper generation supports multiple academic venues via templates.
Add a template
ari-skill-paper/templates/
├── arxiv/
│ └── main.tex ← Already exists
├── neurips/
│ └── main.tex ← Already exists
└── your_venue/
└── main.tex ← Add hereRegister in venue list
In ari-skill-paper/src/server.py, add to VENUES:
VENUES = [
{"id": "neurips", "pages": 9},
{"id": "icpp", "pages": 10},
{"id": "sc", "pages": 12},
{"id": "isc", "pages": 12},
{"id": "arxiv", "pages": 0}, # unlimited
{"id": "acm", "pages": 10},
{"id": "your_venue", "pages": 8}, # ← Add
]Use in pipeline
- stage: generate_paper
skill: ari-skill-paper
tool: generate_section
args:
venue: your_venue # ← Specify here6. Adding Multi-Node / Distributed Experiments
For experiments that need multiple compute nodes simultaneously.
In experiment.md:
## SLURM Script Template
```bash
#!/bin/bash
#SBATCH --nodes=4
#SBATCH --ntasks-per-node=32
#SBATCH --cpus-per-task=2
mpirun -np 128 ./my_parallel_program
In `config/bfts.yaml`, increase timeout:
```yaml
bfts:
timeout_per_node: 3600 # 1 hour for large MPI jobs7. Exposing ARI to External Systems
Use ari-skill-orchestrator to trigger ARI from other agents, IDEs, or scripts. The orchestrator supports dual transport: stdio (MCP for Claude Desktop) + HTTP (REST + SSE on ARI_ORCHESTRATOR_PORT, default 9890).
From Claude Desktop
{
"mcpServers": {
"ari": {
"command": "python",
"args": ["/path/to/ari-skill-orchestrator/src/server.py"]
}
}
}Then in Claude Desktop:
"Run a matrix benchmark and report the best GFLOPS"
From another agent
from mcp import ClientSession
async with ClientSession(...) as session:
result = await session.call_tool("run_experiment", {
"experiment_md": open("experiment.md").read(),
"max_nodes": 10
})
run_id = result["run_id"]Recursive Sub-Experiments
The orchestrator supports parent-child experiment tracking. Child experiments can be launched from a parent:
result = await session.call_tool("run_experiment", {
"experiment_md": "...",
"parent_run_id": "parent_20260414",
"max_recursion_depth": 2
})Use list_children(run_id) to retrieve child runs. The GUI Sub-Experiments page visualizes the hierarchy.
As a REST API (via HTTP transport)
When launched with HTTP transport enabled (ARI_ORCHESTRATOR_PORT), the orchestrator exposes REST endpoints and SSE for CI/CD integration:
# Launch an experiment
curl -X POST http://localhost:9890/run -d '{"experiment_md": "...", "max_nodes": 10}'
# Check status
curl http://localhost:9890/status/{run_id}8. Changing the BFTS Selection Strategy
The current strategy selects nodes with has_real_data=True and the highest metric values. To change this, modify ari/orchestrator/bfts.py:
def _select_best_node(self, nodes: list[Node]) -> Node:
"""
Custom selection strategy.
Default: highest metric among nodes with real data.
"""
candidates = [n for n in nodes if n.has_real_data]
if not candidates:
return nodes[0]
# Example: Pareto-optimal selection for multi-objective
return pareto_select(candidates, objectives=["MFLOPS", "energy"])Extension Anti-Patterns
| Anti-Pattern | Why it's Wrong | Correct Approach |
|---|---|---|
Add domain logic to ari-core | Breaks P1 (generic core) | Put it in experiment.md |
| Call LLM inside a skill tool | Breaks P2 (deterministic tools) | Call only in post-BFTS pipeline |
| Return a scalar score from evaluator | Breaks P3 (multi-objective) | Return full metrics dict |
| Hardcode model name in skill | Breaks P4 (DI) | Pass via config or tool argument |
| Use relative paths in SBATCH | Causes path errors on compute nodes | Always use absolute paths |
Public API + Prompts / Configs (v0.7+)
ari.public.* — the only stable surface for skills
ARI core is free to refactor its internal modules. Skills must only import via the re-export layer:
| Skill use case | Public import |
|---|---|
| Cost-tracker integration | from ari.public import cost_tracker |
| Container runtime helpers | from ari.public import container |
| Path / checkpoint resolution | from ari.public.paths import PathManager |
| LLM client | from ari.public.llm import LLMClient |
| Pydantic config models | from ari.public.config_schema import ARIConfig, LLMConfig, ... |
ari-core/tests/test_public_api_boundary.py walks every ari-skill-*/{src,tests}/ Python file with AST and fails on imports outside ari.public.*.
ari/prompts/ — every LLM prompt lives here
Each prompt is a Markdown file with {var} placeholders that the call-site fills via str.format(). Adding a new core LLM call means:
Drop the template in
ari-core/ari/prompts/<area>/<purpose>.md(e.g.ari/prompts/orchestrator/lineage_decision.md).Load it in the call-site:
pythonfrom ari.prompts import FilesystemPromptLoader prompt = FilesystemPromptLoader().load("orchestrator/lineage_decision")Pin a sha256 row in
ari-core/tests/test_prompt_extraction.pyso a silent edit surfaces as a CI failure.
Conventions:
- Use
str.format()placeholders ({var_name}); no Jinja, no conditional / loop logic inside templates. - Preserve every byte (newlines, leading spaces, trailing newline) of the original Python constant — the byte-equivalence is what makes the LLM output deterministic.
- Skills already follow the same pattern via their own
src/prompts/directories (seeari-skill-replicate,ari-skill-paper-re); keep using that layout for new skills.
ari/configs/ — non-Pydantic lookup tables
Tables that change without code changes (model prices, default model names) live as YAML under ari-core/ari/configs/:
| File | Owner |
|---|---|
model_prices.yaml | LLM cost estimation (ari/cost_tracker.py) |
defaults.yaml | Default model fall-backs (e.g. lineage_decision_default) |
Read via from ari.configs import FilesystemConfigLoader; loader.load("model_prices").
ari/migrations/v05_to_v07/ — legacy compatibility
v0.5 → v0.7 migration helpers (legacy node_report.json reconstruction, the v0.5 global memory JSONL constant, etc.) are isolated here. v1.0 will drop the package; until then code in this directory must NOT acquire new dependencies on the live runtime.
Versioning and Compatibility
- All skill tool interfaces are versioned via their
pyproject.toml - Breaking changes to tool signatures require a minor version bump
ari-coredepends on skill interfaces, not implementations (loose coupling via MCP)- Adding new optional parameters to tools is always backward-compatible