researchloop 0.1.0__py3-none-any.whl
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.
- researchloop/__init__.py +1 -0
- researchloop/__main__.py +3 -0
- researchloop/cli.py +1138 -0
- researchloop/clusters/__init__.py +4 -0
- researchloop/clusters/monitor.py +199 -0
- researchloop/clusters/ssh.py +183 -0
- researchloop/comms/__init__.py +0 -0
- researchloop/comms/base.py +34 -0
- researchloop/comms/conversation.py +465 -0
- researchloop/comms/ntfy.py +95 -0
- researchloop/comms/router.py +71 -0
- researchloop/comms/slack.py +188 -0
- researchloop/core/__init__.py +0 -0
- researchloop/core/auth.py +78 -0
- researchloop/core/config.py +328 -0
- researchloop/core/credentials.py +38 -0
- researchloop/core/models.py +119 -0
- researchloop/core/orchestrator.py +910 -0
- researchloop/dashboard/__init__.py +0 -0
- researchloop/dashboard/app.py +15 -0
- researchloop/dashboard/auth.py +60 -0
- researchloop/dashboard/routes.py +912 -0
- researchloop/dashboard/templates/base.html +84 -0
- researchloop/dashboard/templates/login.html +12 -0
- researchloop/dashboard/templates/loop_detail.html +58 -0
- researchloop/dashboard/templates/loops.html +61 -0
- researchloop/dashboard/templates/setup.html +14 -0
- researchloop/dashboard/templates/sprint_detail.html +109 -0
- researchloop/dashboard/templates/sprints.html +48 -0
- researchloop/dashboard/templates/studies.html +18 -0
- researchloop/dashboard/templates/study_detail.html +64 -0
- researchloop/db/__init__.py +5 -0
- researchloop/db/database.py +86 -0
- researchloop/db/migrations.py +172 -0
- researchloop/db/queries.py +351 -0
- researchloop/runner/__init__.py +1 -0
- researchloop/runner/claude.py +169 -0
- researchloop/runner/job_templates/sge.sh.j2 +319 -0
- researchloop/runner/job_templates/slurm.sh.j2 +336 -0
- researchloop/runner/main.py +156 -0
- researchloop/runner/pipeline.py +272 -0
- researchloop/runner/templates/fix_issues.md.j2 +11 -0
- researchloop/runner/templates/idea_generator.md.j2 +16 -0
- researchloop/runner/templates/red_team.md.j2 +15 -0
- researchloop/runner/templates/report.md.j2 +31 -0
- researchloop/runner/templates/research_sprint.md.j2 +51 -0
- researchloop/runner/templates/summarizer.md.j2 +7 -0
- researchloop/runner/upload.py +153 -0
- researchloop/schedulers/__init__.py +11 -0
- researchloop/schedulers/base.py +43 -0
- researchloop/schedulers/local.py +188 -0
- researchloop/schedulers/sge.py +163 -0
- researchloop/schedulers/slurm.py +179 -0
- researchloop/sprints/__init__.py +0 -0
- researchloop/sprints/auto_loop.py +458 -0
- researchloop/sprints/manager.py +750 -0
- researchloop/studies/__init__.py +0 -0
- researchloop/studies/manager.py +102 -0
- researchloop-0.1.0.dist-info/METADATA +596 -0
- researchloop-0.1.0.dist-info/RECORD +63 -0
- researchloop-0.1.0.dist-info/WHEEL +4 -0
- researchloop-0.1.0.dist-info/entry_points.txt +3 -0
- researchloop-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Core domain models for researchloop."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import secrets
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SprintStatus(str, Enum):
|
|
13
|
+
"""Lifecycle states for a research sprint."""
|
|
14
|
+
|
|
15
|
+
PENDING = "pending"
|
|
16
|
+
SUBMITTED = "submitted"
|
|
17
|
+
RUNNING = "running"
|
|
18
|
+
RESEARCH = "research"
|
|
19
|
+
RED_TEAM = "red_team"
|
|
20
|
+
FIXING = "fixing"
|
|
21
|
+
VALIDATING = "validating"
|
|
22
|
+
REPORTING = "reporting"
|
|
23
|
+
SUMMARIZING = "summarizing"
|
|
24
|
+
UPLOADING = "uploading"
|
|
25
|
+
COMPLETED = "completed"
|
|
26
|
+
FAILED = "failed"
|
|
27
|
+
CANCELLED = "cancelled"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_sprint_id() -> str:
|
|
31
|
+
"""Generate a short hex sprint ID like ``sp-a3f7b2``."""
|
|
32
|
+
return f"sp-{secrets.token_hex(3)}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def format_sprint_dirname(sprint_id: str, idea: str | None) -> str:
|
|
36
|
+
"""Create a directory name for a sprint.
|
|
37
|
+
|
|
38
|
+
Format: ``2026-03-15--19-50--sp-a3f7b2--feature-absorption``
|
|
39
|
+
|
|
40
|
+
Date comes first so ``ls`` shows sprints in chronological order.
|
|
41
|
+
"""
|
|
42
|
+
now = datetime.now(timezone.utc)
|
|
43
|
+
date_part = now.strftime("%Y-%m-%d")
|
|
44
|
+
time_part = now.strftime("%H-%M")
|
|
45
|
+
# Slugify the idea: lowercase, replace non-alnum with hyphens, collapse
|
|
46
|
+
slug = re.sub(r"[^a-z0-9]+", "-", (idea or "auto").lower()).strip("-")
|
|
47
|
+
slug = re.sub(r"-{2,}", "-", slug)
|
|
48
|
+
# Truncate slug to keep directory names reasonable
|
|
49
|
+
slug = slug[:60]
|
|
50
|
+
return f"{date_part}--{time_part}--{sprint_id}--{slug}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _utcnow() -> datetime:
|
|
54
|
+
return datetime.now(timezone.utc)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Sprint:
|
|
59
|
+
"""A single research sprint."""
|
|
60
|
+
|
|
61
|
+
id: str
|
|
62
|
+
study_name: str
|
|
63
|
+
idea: str | None
|
|
64
|
+
status: SprintStatus = SprintStatus.PENDING
|
|
65
|
+
job_id: str | None = None
|
|
66
|
+
directory: str | None = None
|
|
67
|
+
created_at: datetime = field(default_factory=_utcnow)
|
|
68
|
+
started_at: datetime | None = None
|
|
69
|
+
completed_at: datetime | None = None
|
|
70
|
+
error: str | None = None
|
|
71
|
+
summary: str | None = None
|
|
72
|
+
artifacts: list[str] = field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Study:
|
|
77
|
+
"""A research study (read-only view)."""
|
|
78
|
+
|
|
79
|
+
name: str
|
|
80
|
+
cluster: str
|
|
81
|
+
description: str = ""
|
|
82
|
+
sprint_count: int = 0
|
|
83
|
+
active_sprints: int = 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class AutoLoop:
|
|
88
|
+
"""An automated multi-sprint loop."""
|
|
89
|
+
|
|
90
|
+
id: str
|
|
91
|
+
study_name: str
|
|
92
|
+
total_count: int
|
|
93
|
+
completed_count: int = 0
|
|
94
|
+
current_sprint_id: str | None = None
|
|
95
|
+
status: SprintStatus = SprintStatus.PENDING
|
|
96
|
+
created_at: datetime = field(default_factory=_utcnow)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class Artifact:
|
|
101
|
+
"""A file artifact produced by a sprint."""
|
|
102
|
+
|
|
103
|
+
id: str
|
|
104
|
+
sprint_id: str
|
|
105
|
+
filename: str
|
|
106
|
+
path: str
|
|
107
|
+
size: int = 0
|
|
108
|
+
uploaded_at: datetime = field(default_factory=_utcnow)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class Event:
|
|
113
|
+
"""An event recorded during sprint execution."""
|
|
114
|
+
|
|
115
|
+
id: str
|
|
116
|
+
sprint_id: str
|
|
117
|
+
event_type: str
|
|
118
|
+
data: dict = field(default_factory=dict)
|
|
119
|
+
created_at: datetime = field(default_factory=_utcnow)
|