researchloop 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. researchloop-0.1.0/.github/workflows/ci.yml +73 -0
  2. researchloop-0.1.0/.github/workflows/docs.yml +52 -0
  3. researchloop-0.1.0/.github/workflows/release.yml +41 -0
  4. researchloop-0.1.0/.gitignore +55 -0
  5. researchloop-0.1.0/CLAUDE.md +170 -0
  6. researchloop-0.1.0/Dockerfile +43 -0
  7. researchloop-0.1.0/LICENSE +21 -0
  8. researchloop-0.1.0/PKG-INFO +596 -0
  9. researchloop-0.1.0/README.md +566 -0
  10. researchloop-0.1.0/docs/cli.md +231 -0
  11. researchloop-0.1.0/docs/configuration.md +176 -0
  12. researchloop-0.1.0/docs/dashboard.md +115 -0
  13. researchloop-0.1.0/docs/deployment.md +195 -0
  14. researchloop-0.1.0/docs/development.md +186 -0
  15. researchloop-0.1.0/docs/getting-started.md +180 -0
  16. researchloop-0.1.0/docs/index.md +73 -0
  17. researchloop-0.1.0/docs/security.md +120 -0
  18. researchloop-0.1.0/docs/slack.md +133 -0
  19. researchloop-0.1.0/mkdocs.yml +53 -0
  20. researchloop-0.1.0/pyproject.toml +67 -0
  21. researchloop-0.1.0/researchloop/__init__.py +1 -0
  22. researchloop-0.1.0/researchloop/__main__.py +3 -0
  23. researchloop-0.1.0/researchloop/cli.py +1138 -0
  24. researchloop-0.1.0/researchloop/clusters/__init__.py +4 -0
  25. researchloop-0.1.0/researchloop/clusters/monitor.py +199 -0
  26. researchloop-0.1.0/researchloop/clusters/ssh.py +183 -0
  27. researchloop-0.1.0/researchloop/comms/__init__.py +0 -0
  28. researchloop-0.1.0/researchloop/comms/base.py +34 -0
  29. researchloop-0.1.0/researchloop/comms/conversation.py +465 -0
  30. researchloop-0.1.0/researchloop/comms/ntfy.py +95 -0
  31. researchloop-0.1.0/researchloop/comms/router.py +71 -0
  32. researchloop-0.1.0/researchloop/comms/slack.py +188 -0
  33. researchloop-0.1.0/researchloop/core/__init__.py +0 -0
  34. researchloop-0.1.0/researchloop/core/auth.py +78 -0
  35. researchloop-0.1.0/researchloop/core/config.py +328 -0
  36. researchloop-0.1.0/researchloop/core/credentials.py +38 -0
  37. researchloop-0.1.0/researchloop/core/models.py +119 -0
  38. researchloop-0.1.0/researchloop/core/orchestrator.py +910 -0
  39. researchloop-0.1.0/researchloop/dashboard/__init__.py +0 -0
  40. researchloop-0.1.0/researchloop/dashboard/app.py +15 -0
  41. researchloop-0.1.0/researchloop/dashboard/auth.py +60 -0
  42. researchloop-0.1.0/researchloop/dashboard/routes.py +912 -0
  43. researchloop-0.1.0/researchloop/dashboard/templates/base.html +84 -0
  44. researchloop-0.1.0/researchloop/dashboard/templates/login.html +12 -0
  45. researchloop-0.1.0/researchloop/dashboard/templates/loop_detail.html +58 -0
  46. researchloop-0.1.0/researchloop/dashboard/templates/loops.html +61 -0
  47. researchloop-0.1.0/researchloop/dashboard/templates/setup.html +14 -0
  48. researchloop-0.1.0/researchloop/dashboard/templates/sprint_detail.html +109 -0
  49. researchloop-0.1.0/researchloop/dashboard/templates/sprints.html +48 -0
  50. researchloop-0.1.0/researchloop/dashboard/templates/studies.html +18 -0
  51. researchloop-0.1.0/researchloop/dashboard/templates/study_detail.html +64 -0
  52. researchloop-0.1.0/researchloop/db/__init__.py +5 -0
  53. researchloop-0.1.0/researchloop/db/database.py +86 -0
  54. researchloop-0.1.0/researchloop/db/migrations.py +172 -0
  55. researchloop-0.1.0/researchloop/db/queries.py +351 -0
  56. researchloop-0.1.0/researchloop/runner/__init__.py +1 -0
  57. researchloop-0.1.0/researchloop/runner/claude.py +169 -0
  58. researchloop-0.1.0/researchloop/runner/job_templates/sge.sh.j2 +319 -0
  59. researchloop-0.1.0/researchloop/runner/job_templates/slurm.sh.j2 +336 -0
  60. researchloop-0.1.0/researchloop/runner/main.py +156 -0
  61. researchloop-0.1.0/researchloop/runner/pipeline.py +272 -0
  62. researchloop-0.1.0/researchloop/runner/templates/fix_issues.md.j2 +11 -0
  63. researchloop-0.1.0/researchloop/runner/templates/idea_generator.md.j2 +16 -0
  64. researchloop-0.1.0/researchloop/runner/templates/red_team.md.j2 +15 -0
  65. researchloop-0.1.0/researchloop/runner/templates/report.md.j2 +31 -0
  66. researchloop-0.1.0/researchloop/runner/templates/research_sprint.md.j2 +51 -0
  67. researchloop-0.1.0/researchloop/runner/templates/summarizer.md.j2 +7 -0
  68. researchloop-0.1.0/researchloop/runner/upload.py +153 -0
  69. researchloop-0.1.0/researchloop/schedulers/__init__.py +11 -0
  70. researchloop-0.1.0/researchloop/schedulers/base.py +43 -0
  71. researchloop-0.1.0/researchloop/schedulers/local.py +188 -0
  72. researchloop-0.1.0/researchloop/schedulers/sge.py +163 -0
  73. researchloop-0.1.0/researchloop/schedulers/slurm.py +179 -0
  74. researchloop-0.1.0/researchloop/sprints/__init__.py +0 -0
  75. researchloop-0.1.0/researchloop/sprints/auto_loop.py +458 -0
  76. researchloop-0.1.0/researchloop/sprints/manager.py +750 -0
  77. researchloop-0.1.0/researchloop/studies/__init__.py +0 -0
  78. researchloop-0.1.0/researchloop/studies/manager.py +102 -0
  79. researchloop-0.1.0/researchloop.toml.example +91 -0
  80. researchloop-0.1.0/slack-app-manifest.yml +32 -0
  81. researchloop-0.1.0/tests/__init__.py +0 -0
  82. researchloop-0.1.0/tests/conftest.py +104 -0
  83. researchloop-0.1.0/tests/docker/slurm/Dockerfile +28 -0
  84. researchloop-0.1.0/tests/docker/slurm/entrypoint.sh +21 -0
  85. researchloop-0.1.0/tests/docker/slurm/mock_claude.sh +41 -0
  86. researchloop-0.1.0/tests/integration/__init__.py +0 -0
  87. researchloop-0.1.0/tests/integration/conftest.py +288 -0
  88. researchloop-0.1.0/tests/integration/test_loop_advancement.py +687 -0
  89. researchloop-0.1.0/tests/integration/test_loop_and_monitor.py +341 -0
  90. researchloop-0.1.0/tests/integration/test_slurm_scheduler.py +175 -0
  91. researchloop-0.1.0/tests/integration/test_sprint_slurm.py +302 -0
  92. researchloop-0.1.0/tests/integration/test_webhook_and_refresh.py +693 -0
  93. researchloop-0.1.0/tests/test_api.py +601 -0
  94. researchloop-0.1.0/tests/test_auto_loop.py +668 -0
  95. researchloop-0.1.0/tests/test_cli.py +357 -0
  96. researchloop-0.1.0/tests/test_config.py +200 -0
  97. researchloop-0.1.0/tests/test_conversation.py +366 -0
  98. researchloop-0.1.0/tests/test_dashboard.py +465 -0
  99. researchloop-0.1.0/tests/test_database.py +78 -0
  100. researchloop-0.1.0/tests/test_models.py +63 -0
  101. researchloop-0.1.0/tests/test_notification.py +58 -0
  102. researchloop-0.1.0/tests/test_queries.py +242 -0
  103. researchloop-0.1.0/tests/test_runner.py +110 -0
  104. researchloop-0.1.0/tests/test_schedulers.py +167 -0
  105. researchloop-0.1.0/tests/test_sge.py +255 -0
  106. researchloop-0.1.0/tests/test_slack.py +180 -0
  107. researchloop-0.1.0/tests/test_slack_events.py +420 -0
  108. researchloop-0.1.0/tests/test_sprint_manager.py +618 -0
  109. researchloop-0.1.0/tests/test_study_manager.py +77 -0
  110. researchloop-0.1.0/uv.lock +1609 -0
@@ -0,0 +1,73 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v4
17
+
18
+ - name: Set up Python
19
+ run: uv python install 3.12
20
+
21
+ - name: Install dependencies
22
+ run: uv sync
23
+
24
+ - name: Ruff check
25
+ run: uv run ruff check .
26
+
27
+ - name: Ruff format check
28
+ run: uv run ruff format --check .
29
+
30
+ - name: Pyright type check
31
+ run: uv run pyright researchloop/
32
+
33
+ test:
34
+ runs-on: ubuntu-latest
35
+ strategy:
36
+ matrix:
37
+ python-version: ["3.10", "3.12", "3.13"]
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@v4
43
+
44
+ - name: Set up Python ${{ matrix.python-version }}
45
+ run: uv python install ${{ matrix.python-version }}
46
+
47
+ - name: Install dependencies
48
+ run: uv sync
49
+
50
+ - name: Run tests
51
+ run: uv run pytest tests/ -v --tb=short -m "not integration"
52
+
53
+ integration:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - name: Install uv
59
+ uses: astral-sh/setup-uv@v4
60
+
61
+ - name: Set up Python
62
+ run: uv python install 3.12
63
+
64
+ - name: Install dependencies
65
+ run: uv sync
66
+
67
+ - name: Build SLURM test container
68
+ run: docker build -t researchloop-slurm-test tests/docker/slurm/
69
+
70
+ - name: Run integration tests
71
+ run: uv run pytest tests/integration/ -v --tb=short -x --timeout=120
72
+ env:
73
+ SLURM_TEST_IMAGE: researchloop-slurm-test
@@ -0,0 +1,52 @@
1
+ name: Docs
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main]
7
+ paths:
8
+ - "docs/**"
9
+ - "mkdocs.yml"
10
+ - "README.md"
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: "pages"
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ deploy:
23
+ runs-on: ubuntu-latest
24
+ environment:
25
+ name: github-pages
26
+ url: ${{ steps.deployment.outputs.page_url }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v4
32
+
33
+ - name: Set up Python
34
+ run: uv python install 3.12
35
+
36
+ - name: Install docs dependencies
37
+ run: uv venv /tmp/docs-venv && uv pip install --python /tmp/docs-venv/bin/python mkdocs-material
38
+
39
+ - name: Build docs
40
+ run: /tmp/docs-venv/bin/mkdocs build --strict
41
+
42
+ - name: Setup Pages
43
+ uses: actions/configure-pages@v5
44
+
45
+ - name: Upload artifact
46
+ uses: actions/upload-pages-artifact@v3
47
+ with:
48
+ path: site
49
+
50
+ - name: Deploy to GitHub Pages
51
+ id: deployment
52
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,41 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ release:
14
+ runs-on: ubuntu-latest
15
+ environment: pypi
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+
22
+ - name: Set up Python
23
+ run: uv python install 3.12
24
+
25
+ - name: Install dependencies
26
+ run: uv sync
27
+
28
+ - name: Run tests
29
+ run: uv run pytest tests/ -v --tb=short -m "not integration"
30
+
31
+ - name: Build package
32
+ run: uv build
33
+
34
+ - name: Publish to PyPI
35
+ uses: pypa/gh-action-pypi-publish@release/v1
36
+
37
+ - name: Create GitHub Release
38
+ uses: softprops/action-gh-release@v2
39
+ with:
40
+ generate_release_notes: true
41
+ files: dist/*
@@ -0,0 +1,55 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+ env/
11
+ ENV/
12
+
13
+ # Distribution / packaging
14
+ dist/
15
+ build/
16
+ *.egg-info/
17
+ *.egg
18
+
19
+ # Environment files
20
+ .env
21
+ .env.*
22
+
23
+ # Databases
24
+ *.db
25
+ *.sqlite
26
+ *.sqlite3
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+ *~
34
+
35
+ # OS
36
+ .DS_Store
37
+ Thumbs.db
38
+
39
+ # Testing / coverage
40
+ .coverage
41
+ htmlcov/
42
+ .pytest_cache/
43
+
44
+ # Type checking
45
+ .pyright/
46
+ .mypy_cache/
47
+
48
+ # Ruff
49
+ .ruff_cache/
50
+
51
+ # Artifacts
52
+ artifacts/
53
+
54
+ # MkDocs
55
+ site/
@@ -0,0 +1,170 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project overview
4
+
5
+ ResearchLoop is an automated research sprint platform for HPC clusters. It orchestrates multi-step AI research pipelines on SLURM/SGE clusters using `claude -p` for all AI work. The orchestrator is a lightweight Docker container; all heavy compute runs on HPC.
6
+
7
+ ## Architecture
8
+
9
+ Two processes:
10
+
11
+ 1. **Orchestrator** (`researchloop serve`) — FastAPI server that manages studies/sprints in SQLite, submits jobs via SSH, receives webhooks from runners, stores artifacts. Also serves the web dashboard and handles Slack events.
12
+ 2. **Sprint Runner** — runs inside each SLURM/SGE job on HPC. Self-contained bash scripts chain `claude -p` calls through a pipeline (research → red-team → fix → report → summarize), then upload artifacts and send a completion webhook.
13
+
14
+ Key design decisions:
15
+
16
+ - All AI work runs on HPC, never on the orchestrator (except Slack conversations and auto-loop idea generation, which use `claude -p` locally with restricted tools)
17
+ - `claude -p --output-format stream-json` for sprint steps (enables live progress), `--output-format json` for conversations
18
+ - SSH to HPC login nodes for sbatch/squeue/scancel/qsub/qdel
19
+ - Job completion via per-sprint webhook tokens (runner → orchestrator), SSH polling as fallback
20
+ - SQLite (aiosqlite, WAL mode) for metadata, with a `settings` table for persistent config (signing key, password hash)
21
+ - Jinja2 templates for all prompts and job scripts — prompts are pre-rendered by the orchestrator and embedded as base64 in the job script
22
+ - Auto-loop sprints generate their own ideas on the cluster (where Claude is authenticated) rather than on the orchestrator
23
+ - Context hierarchy: global → cluster → study (inline text + file paths at each level)
24
+
25
+ ## Tech stack
26
+
27
+ Python 3.10+, uv, asyncio throughout. Key deps: click (CLI), FastAPI (API + dashboard), aiosqlite (DB), asyncssh (SSH), httpx (HTTP client), Jinja2 (templates), bcrypt + itsdangerous (dashboard auth), markdown (report rendering in dashboard).
28
+
29
+ ## Commands
30
+
31
+ ```bash
32
+ uv sync # install deps
33
+ uv run pytest tests/ -v -m "not integration" # unit tests (339 tests, ~3s)
34
+ uv run pytest tests/integration/ -v --timeout=120 # integration tests (needs Docker)
35
+ uv run ruff check . # lint
36
+ uv run ruff format . # format
37
+ uv run pyright researchloop/ # type check
38
+ uv run researchloop --help # CLI help
39
+ ```
40
+
41
+ ## Package layout
42
+
43
+ ```
44
+ researchloop/
45
+ __init__.py — __version__
46
+ __main__.py — python -m researchloop entry point
47
+ cli.py — Click CLI (init, serve, connect, disconnect, status, study, sprint, loop, cluster commands)
48
+ core/
49
+ __init__.py
50
+ config.py — TOML config loading into dataclasses (Config, ClusterConfig, StudyConfig, SlackConfig, NtfyConfig, DashboardConfig) + env var overrides
51
+ models.py — SprintStatus enum, Sprint/Study/AutoLoop dataclasses, generate_sprint_id(), format_sprint_dirname()
52
+ orchestrator.py — Orchestrator class + create_app() FastAPI factory (API + Slack + dashboard)
53
+ credentials.py — CLI credential storage (~/.config/researchloop/credentials.json) for remote orchestrator auth
54
+ auth.py — check_claude_auth_async() helper for verifying Claude CLI auth status
55
+ db/
56
+ __init__.py
57
+ database.py — async SQLite wrapper (WAL mode, auto-migrations, fetch_one/fetch_all/execute)
58
+ migrations.py — CREATE TABLE statements (7 tables: studies, sprints, auto_loops, artifacts, slack_sessions, events, settings) + indexes + incremental column migrations
59
+ queries.py — async CRUD functions (all take Database as first arg, return dicts)
60
+ clusters/
61
+ __init__.py
62
+ ssh.py — SSHConnection (connect/run/upload_file/download_file) + SSHManager (connection pooling via asyncssh)
63
+ monitor.py — JobMonitor (polls active jobs via SSH, detects abandoned sprints via heartbeat timeout)
64
+ schedulers/
65
+ __init__.py
66
+ base.py — BaseScheduler ABC (submit/status/cancel)
67
+ slurm.py — SlurmScheduler (sbatch/squeue/sacct/scancel)
68
+ sge.py — SGEScheduler (qsub/qstat/qacct/qdel)
69
+ local.py — LocalScheduler (subprocesses, for testing)
70
+ sprints/
71
+ __init__.py
72
+ manager.py — SprintManager (create/submit/run/cancel/handle_completion + PDF fetch + idea fetch)
73
+ auto_loop.py — AutoLoopController (start/stop/resume/on_sprint_complete, LLM idea generation)
74
+ studies/
75
+ __init__.py
76
+ manager.py — StudyManager (config→DB sync, cluster config resolution)
77
+ runner/
78
+ __init__.py
79
+ main.py — Runner CLI entry point (researchloop-runner run)
80
+ pipeline.py — Pipeline class (runs the multi-step research pipeline)
81
+ claude.py — run_claude() wrapper + render_template()
82
+ upload.py — upload_artifacts(), send_webhook(), send_heartbeat()
83
+ templates/ — 6 Jinja2 prompt templates:
84
+ research_sprint.md.j2 — main research prompt (includes progress.md + output.log instructions)
85
+ red_team.md.j2 — critique/red-team prompt
86
+ fix_issues.md.j2 — fix prompt after red-team
87
+ report.md.j2 — comprehensive report generation
88
+ summarizer.md.j2 — short summary for notifications
89
+ idea_generator.md.j2 — next idea generation for auto-loops
90
+ job_templates/ — 2 job script templates:
91
+ slurm.sh.j2 — self-contained SLURM job script (includes stream-json processing, heartbeat loop, prompt embedding)
92
+ sge.sh.j2 — SGE equivalent
93
+ comms/
94
+ __init__.py
95
+ base.py — BaseNotifier ABC (notify_sprint_started/completed/failed)
96
+ ntfy.py — NtfyNotifier (ntfy.sh push notifications)
97
+ slack.py — SlackNotifier (chat:write + files:write) + verify_slack_signature()
98
+ conversation.py — ConversationManager (Slack threads → Claude sessions via --resume, action execution, markdown→Slack conversion)
99
+ router.py — NotificationRouter (fan-out to all configured notifiers)
100
+ dashboard/
101
+ __init__.py
102
+ app.py — ASGI app factory for `researchloop serve`
103
+ auth.py — Password auth (bcrypt hash/check, signed session cookies via itsdangerous, CSRF token generation/verification)
104
+ routes.py — Dashboard HTML routes (setup, login/logout, studies, sprints, loops, artifacts, refresh, cancel, delete, resubmit)
105
+ templates/ — 9 Jinja2 HTML templates:
106
+ base.html — layout with nav and auth state
107
+ setup.html — first-run password setup
108
+ login.html — login form
109
+ studies.html — study list
110
+ study_detail.html — study detail + sprint submission form
111
+ sprints.html — sprint list + new sprint form
112
+ sprint_detail.html — sprint detail (status, progress, log, report, artifacts, actions)
113
+ loops.html — auto-loop list + new loop form
114
+ loop_detail.html — loop detail with sprint list
115
+ ```
116
+
117
+ ## Database
118
+
119
+ SQLite with 7 tables: `studies`, `sprints`, `auto_loops`, `artifacts`, `slack_sessions`, `events`, `settings`. Schema in `db/migrations.py`. All queries in `db/queries.py` use parameterized SQL and return plain dicts.
120
+
121
+ Key columns:
122
+ - `sprints.webhook_token` — per-sprint token for webhook auth (generated at creation)
123
+ - `sprints.loop_id` — links sprint to its auto-loop
124
+ - `sprints.metadata_json` — stores report text, has_pdf flag, heartbeat info
125
+ - `sprints.error` — stores live progress (progress.md + output.log + tool log) during running sprints
126
+ - `auto_loops.metadata_json` — stores loop context and job_options
127
+ - `settings` — key/value store for signing_key and dashboard_password_hash
128
+
129
+ ## Key patterns
130
+
131
+ - All source files use `from __future__ import annotations` for 3.10 compat
132
+ - Config is loaded from `researchloop.toml` (TOML) via `core/config.py`, with env var overrides (RESEARCHLOOP_* prefix)
133
+ - Database queries are plain async functions in `db/queries.py`, not methods on Database
134
+ - Schedulers take an SSH connection object but LocalScheduler ignores it
135
+ - SprintManager.submit_sprint() handles the full workflow: render prompt templates → render job template → SSH mkdir → base64 encode + write script → submit → update DB → notify
136
+ - Prompt templates are pre-rendered by the orchestrator and embedded as base64 in the job script, so the runner has no dependency on the orchestrator's template files
137
+ - Context hierarchy: global (config.context + config.context_paths) → cluster (cluster.context + cluster.context_paths) → study (study.context + study.claude_md_path), all concatenated into study_context
138
+ - The job script uses `--output-format stream-json` and pipes through a Python filter that logs tool usage summaries and captures the session ID
139
+ - Claude sessions persist across pipeline steps via `--resume $SESSION_ID`
140
+ - The runner writes `progress.md` (researcher updates) and pipes script output to `output.log` — both are sent via heartbeat and displayed in the dashboard
141
+ - Background heartbeat loop in job script sends log tail + progress.md + output.log + recent files every 60s
142
+ - Webhook retries: completion webhook retries 3 times with 10s delay
143
+ - Per-sprint webhook tokens: each sprint gets a unique token at creation, passed via X-Webhook-Token header
144
+ - Dashboard signing key is auto-generated and persisted in DB `settings` table (survives restarts)
145
+ - Dashboard password can come from: config TOML → env var (RESEARCHLOOP_DASHBOARD_PASSWORD auto-hashes) → DB settings table (set via first-run setup page)
146
+ - CSRF protection: HMAC-based tokens derived from session token + signing secret, checked on all mutating dashboard POST routes
147
+ - Dashboard refresh: pulls live status from cluster via SSH (reads logs, progress.md, output.log, report.md, findings.md, summary.txt, idea.txt, checks for PDF)
148
+ - Slack events: deduplication via event_id set, signature verification, background task processing (return 200 immediately), bot message filtering
149
+ - Slack conversation: thread → session mapping in DB, context building with study/sprint info, action execution via [ACTION: ...] tags
150
+ - Auto-loop: sprint idea=None → job script generates idea on cluster → idea.txt read back via SSH/webhook
151
+ - CLI auth: `researchloop connect` gets a bearer token via /api/auth, stored in ~/.config/researchloop/credentials.json with 600 permissions
152
+ - CLI auto-reauth: on 401, prompts for password, gets new token, saves it
153
+ - Always ensure the codebase passes type checking with pyright and ruff check before committing. Fix any errors that exist.
154
+
155
+ ## Testing
156
+
157
+ 339 unit tests covering: models, config parsing, database operations, all query functions, SLURM scheduler (mock SSH), SGE scheduler (mock SSH), local scheduler (real subprocesses), study/sprint managers, auto-loop controller (with mock claude), notification router, Slack notifier + signature verification + conversation manager + Slack events API, FastAPI API endpoints (TestClient), dashboard routes + auth + setup + CSRF, CLI commands (CliRunner), runner output parsing, and template rendering.
158
+
159
+ Integration tests (in tests/integration/) use a Docker SLURM container to test real job submission.
160
+
161
+ Tests use in-memory SQLite (`:memory:`) and mock SSH via AsyncMock.
162
+
163
+ Always add tests for any new functionality!
164
+
165
+ ## CI
166
+
167
+ GitHub Actions (`.github/workflows/ci.yml`):
168
+ - **lint** — ruff check + ruff format --check + pyright type check
169
+ - **test** — pytest on Python 3.10, 3.12, 3.13 (unit tests only, `-m "not integration"`)
170
+ - **integration** — builds Docker SLURM container, runs integration tests with 120s timeout
@@ -0,0 +1,43 @@
1
+ # Stage 1: Build
2
+ FROM python:3.12-slim AS builder
3
+
4
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
5
+
6
+ WORKDIR /app
7
+
8
+ # Copy project files
9
+ COPY pyproject.toml .
10
+ COPY researchloop/ researchloop/
11
+
12
+ # Install dependencies into a virtual environment
13
+ RUN uv venv /app/.venv && \
14
+ uv pip install --python /app/.venv/bin/python .
15
+
16
+ # Stage 2: Runtime
17
+ FROM python:3.12-slim
18
+
19
+ # Install SSH client (needed to connect to HPC clusters)
20
+ RUN apt-get update && \
21
+ apt-get install -y --no-install-recommends openssh-client curl && \
22
+ rm -rf /var/lib/apt/lists/*
23
+
24
+ # Install Claude CLI (needed for auto-loop idea generation + Slack)
25
+ RUN curl -fsSL https://claude.ai/install.sh | sh || true
26
+
27
+ WORKDIR /app
28
+
29
+ # Copy virtual environment and application from builder
30
+ COPY --from=builder /app/.venv /app/.venv
31
+ COPY --from=builder /app/researchloop /app/researchloop
32
+
33
+ # Put venv and claude on PATH
34
+ ENV PATH="/root/.claude/bin:/app/.venv/bin:$PATH"
35
+
36
+ # Data directory — mount a persistent volume here
37
+ ENV RESEARCHLOOP_DB_PATH="/data/researchloop.db"
38
+ ENV RESEARCHLOOP_ARTIFACT_DIR="/data/artifacts"
39
+ RUN mkdir -p /data/artifacts
40
+
41
+ EXPOSE 8080
42
+
43
+ CMD ["researchloop", "serve"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ResearchLoop Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.