Configuration¶
ResearchLoop is configured via a researchloop.toml file. The config file is searched in this order:
- Explicit path via
--config/-cCLI flag researchloop.tomlin the current working directory~/.config/researchloop/researchloop.toml
Complete reference¶
# ── Top-level settings ──────────────────────────────────────────
db_path = "researchloop.db" # SQLite database location
artifact_dir = "artifacts" # Local directory for uploaded artifacts
shared_secret = "your-secret" # Auth token for runner-to-orchestrator communication
orchestrator_url = "https://example.com" # Public URL where runners send webhooks
claude_command = "" # Override the claude command globally
# Global context included in all sprints (all studies, all clusters)
context = "Always use Python 3.10+ features."
context_paths = ["./global-context.md"] # Files whose contents are included as context
# ── Cluster configuration ───────────────────────────────────────
[[cluster]]
name = "hpc" # Unique cluster identifier
host = "login.cluster.example.com" # SSH hostname
port = 22 # SSH port
user = "researcher" # SSH username
key_path = "~/.ssh/id_ed25519" # Path to SSH private key
scheduler_type = "slurm" # "slurm", "sge", or "local"
working_dir = "/scratch/user/researchloop" # Base directory on the cluster
max_concurrent_jobs = 4 # Max simultaneous jobs on this cluster
# Override the claude invocation for this cluster. Leave unset to use the
# default, which is `claude --dangerously-skip-permissions` plus a set of
# `--disallowedTools` (Task, Monitor, PushNotification, Cron*, AskUserQuestion,
# EnterPlanMode, ExitPlanMode, RemoteTrigger) — interactive/agentic tools that
# don't work under `claude -p`. Set this only if you need a custom invocation
# (e.g. `singularity exec img.sif claude ...`); overrides manage their own flags.
# claude_command = "claude --dangerously-skip-permissions"
# Cluster-specific context (appended after global context)
context = "GPUs are NVIDIA L40. Check CUDA_VISIBLE_DEVICES before running."
context_paths = ["./cluster-notes.md"]
# Environment variables injected into job scripts
[cluster.environment]
# ANTHROPIC_API_KEY = "sk-ant-..." # Only needed if not using claude login
# SLURM/SGE job options (passed as #SBATCH or #$ directives)
[cluster.job_options]
gres = "gpu:l40:1"
cpus-per-task = "8"
mem = "64G"
# Resource presets: named bundles that appear as a "Preset" dropdown in the
# sprint/loop/tweak forms and prefill the resource fields with one click.
# Add as many [[cluster.preset]] tables as you like per cluster.
[[cluster.preset]]
name = "H100 1 GPU"
gpu = "gpu:h100:1" # alias: gres
mem = "128G"
cpus = "16" # alias: cpus-per-task
time = "8:00:00" # alias: time_limit
extra_options = "--partition=gpu" # alias: extra
[[cluster.preset]]
name = "CPU only" # omitted fields are blanked when applied,
cpus = "8" # so this clears the cluster's default GPU
mem = "32G"
# ── Study configuration ─────────────────────────────────────────
[[study]]
name = "my-study" # Unique study identifier
cluster = "hpc" # Must match a [[cluster]] name
description = "Research into X" # Human-readable description
# Study context (appended after global + cluster context)
claude_md_path = "./studies/my-study/CLAUDE.md" # File with study-specific context
context = """
Focus on improving F1 score. Use batch size 1024.
"""
# Sprint settings
sprints_dir = "/scratch/user/my-study" # Where sprint dirs are created
# Default: <working_dir>/<study_name>
max_sprint_duration_hours = 8 # SLURM --time limit
red_team_max_rounds = 3 # Number of red-team/fix cycles
allow_loop = true # Whether auto-loops are allowed
# Override claude command for this study
claude_command = ""
# Per-study SLURM/SGE overrides (merged with cluster.job_options)
[study.job_options]
gres = "gpu:a100:2"
# ── Notifications ────────────────────────────────────────────────
[ntfy]
url = "https://ntfy.sh" # ntfy server URL (default: ntfy.sh)
topic = "researchloop" # ntfy topic for push notifications
[slack]
bot_token = "" # Slack Bot User OAuth Token (xoxb-...)
signing_secret = "" # Slack Signing Secret
channel_id = "C0123456789" # Channel or user ID for notifications
allowed_user_ids = ["U0123456789"] # Users allowed to interact with bot
restrict_to_channel = false # If true, only respond in channel_id
# (DMs are always allowed)
# ── Dashboard ────────────────────────────────────────────────────
[dashboard]
enabled = true # Enable the web dashboard
host = "0.0.0.0" # Bind address
port = 8080 # Bind port
password_hash = "" # bcrypt hash (prefer env var or setup page)
Context hierarchy¶
Context is assembled from multiple sources and concatenated in this order:
- Global context --
contextand files fromcontext_pathsat the top level - Cluster context --
contextand files fromcontext_pathson the cluster - Study context --
contextinline text and the file atclaude_md_path
The combined context is:
- Included in all sprint research prompts
- Written as CLAUDE.md to the sprint directory on the cluster (so Claude CLI picks it up automatically)
- Used by the auto-loop idea generator
Environment variable overrides¶
All secrets and sensitive settings can be set via environment variables with the RESEARCHLOOP_ prefix. Environment variables take precedence over TOML values.
| Environment variable | Overrides |
|---|---|
RESEARCHLOOP_SHARED_SECRET |
shared_secret |
RESEARCHLOOP_ORCHESTRATOR_URL |
orchestrator_url |
RESEARCHLOOP_DB_PATH |
db_path |
RESEARCHLOOP_ARTIFACT_DIR |
artifact_dir |
RESEARCHLOOP_SLACK_BOT_TOKEN |
slack.bot_token |
RESEARCHLOOP_SLACK_SIGNING_SECRET |
slack.signing_secret |
RESEARCHLOOP_SLACK_CHANNEL_ID |
slack.channel_id |
RESEARCHLOOP_SLACK_ALLOWED_USER_IDS |
slack.allowed_user_ids (comma-separated) |
RESEARCHLOOP_NTFY_TOPIC |
ntfy.topic |
RESEARCHLOOP_NTFY_URL |
ntfy.url |
RESEARCHLOOP_DASHBOARD_PASSWORD |
Auto-hashed with bcrypt on startup |
RESEARCHLOOP_DASHBOARD_PASSWORD_HASH |
dashboard.password_hash |
RESEARCHLOOP_DASHBOARD_PORT |
dashboard.port |
RESEARCHLOOP_DASHBOARD_HOST |
dashboard.host |
Recommended approach
Keep your researchloop.toml with only structural config (clusters, studies, context). Put secrets in environment variables or your deployment platform's secret manager.
Study CLAUDE.md¶
Each study can have a CLAUDE.md file that provides domain-specific context to Claude. This file is included in every sprint's research prompt and written to the sprint directory on the cluster.
Scaffold a starter file:
This creates studies/my-study/CLAUDE.md with sections for:
- Overview -- what you're studying
- Background -- key papers, prior findings, domain knowledge
- Codebase -- existing code, data formats, infrastructure
- Goals -- what you're trying to learn or build
- Constraints -- rules for the sprints to follow
Job options¶
Job options are merged from three levels (later values override earlier):
cluster.job_options-- defaults for all studies on this clusterstudy.job_options-- overrides for a specific study- Per-sprint overrides -- from the dashboard form or API request
Common SLURM options:
| Option | Example | Description |
|---|---|---|
gres |
gpu:l40:1 |
GPU resources |
mem |
64G |
Memory limit |
cpus-per-task |
8 |
CPU cores |
partition |
gpu |
SLURM partition |
qos |
high |
Quality of service |
Resource presets¶
Each cluster can define named resource presets via [[cluster.preset]]
tables. Presets show up as a Preset dropdown in the sprint, auto-loop, and
tweak forms; picking one prefills the GPU, memory, CPU, time-limit, and
additional-options fields so you don't have to type common hardware setups by
hand (e.g. "H100 1 GPU", "L40S 2 GPUs", "CPU only").
[[cluster.preset]]
name = "H100 1 GPU"
gpu = "gpu:h100:1" # alias: gres
mem = "128G"
cpus = "16" # alias: cpus-per-task
time = "8:00:00" # alias: time_limit
extra_options = "--partition=gpu" # alias: extra
[[cluster.preset]]
name = "CPU only"
cpus = "8"
mem = "32G"
Presets are not a merge layer — selecting one simply fills the form fields,
exactly as if you had typed the values. Any field you omit is left blank when
the preset is applied, so a "CPU only" preset (no gpu) clears the cluster's
default GPU. You can still edit the fields afterward; switching the dropdown
back to "Custom…" leaves whatever you've typed untouched.