researchloop 0.1.0
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.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/researchloop.js +900 -0
- package/docs/getting-started.md +283 -0
- package/package.json +37 -0
- package/templates/adapters/generic.md +18 -0
- package/templates/adapters/huggingface.md +15 -0
- package/templates/adapters/llm-research-kit.md +20 -0
- package/templates/adapters/pytorch.md +26 -0
- package/templates/base/AGENTS.md +47 -0
- package/templates/base/goal.md +22 -0
- package/templates/base/plan.md +22 -0
- package/templates/base/scratchpad/THREAD.md +13 -0
- package/templates/base/scratchpad/audits.md +14 -0
- package/templates/base/scratchpad/ideas/.gitkeep +1 -0
- package/templates/base/scratchpad/papers/.gitkeep +1 -0
- package/templates/base/scratchpad/picklist.md +15 -0
- package/templates/base/scratchpad/runs.jsonl +1 -0
- package/templates/base/scratchpad/sweeps/.gitkeep +1 -0
- package/templates/base/scratchpad/variants/.gitkeep +1 -0
- package/templates/dashboard/index.html +627 -0
- package/templates/prompts/claude-code.md +30 -0
- package/templates/prompts/codex.md +29 -0
- package/templates/prompts/focus/architecture.md +30 -0
- package/templates/prompts/focus/attention.md +27 -0
- package/templates/prompts/focus/hyperparameters.md +32 -0
- package/templates/prompts/generic.md +8 -0
- package/templates/prompts/hermes.md +26 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>ResearchLoop Dashboard</title>
|
|
7
|
+
<meta
|
|
8
|
+
name="description"
|
|
9
|
+
content="Local dashboard for ResearchLoop experiment tracking. No auth, no accounts, localhost only."
|
|
10
|
+
/>
|
|
11
|
+
<style>
|
|
12
|
+
:root {
|
|
13
|
+
color-scheme: dark;
|
|
14
|
+
--bg: #071016;
|
|
15
|
+
--panel: #0d171f;
|
|
16
|
+
--panel-2: #101b25;
|
|
17
|
+
--line: rgba(255, 255, 255, 0.09);
|
|
18
|
+
--text: #edf3f8;
|
|
19
|
+
--muted: #9ba7b2;
|
|
20
|
+
--soft: #cad4dc;
|
|
21
|
+
--accent: #62d6a6;
|
|
22
|
+
--accent-2: #71a7ff;
|
|
23
|
+
--danger: #ff8b8b;
|
|
24
|
+
--warning: #f6c177;
|
|
25
|
+
--radius: 14px;
|
|
26
|
+
--shadow: 0 20px 70px rgba(0, 0, 0, 0.33);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
* {
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
body {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
36
|
+
background:
|
|
37
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 18%),
|
|
38
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
|
39
|
+
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
|
40
|
+
var(--bg);
|
|
41
|
+
background-size: auto, 72px 72px, 72px 72px, auto;
|
|
42
|
+
color: var(--text);
|
|
43
|
+
min-height: 100vh;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
a {
|
|
47
|
+
color: inherit;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.shell {
|
|
51
|
+
width: min(1280px, calc(100% - 32px));
|
|
52
|
+
margin: 0 auto;
|
|
53
|
+
padding: 24px 0 32px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
header {
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 16px;
|
|
61
|
+
margin-bottom: 20px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.brand {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 12px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.brand-mark {
|
|
71
|
+
width: 36px;
|
|
72
|
+
height: 36px;
|
|
73
|
+
border-radius: 10px;
|
|
74
|
+
display: grid;
|
|
75
|
+
place-items: center;
|
|
76
|
+
border: 1px solid rgba(98, 214, 166, 0.26);
|
|
77
|
+
color: var(--accent);
|
|
78
|
+
background: linear-gradient(135deg, rgba(98, 214, 166, 0.13), rgba(113, 167, 255, 0.13));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.brand h1 {
|
|
82
|
+
margin: 0;
|
|
83
|
+
font-size: 17px;
|
|
84
|
+
line-height: 1.2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.brand p {
|
|
88
|
+
margin: 2px 0 0;
|
|
89
|
+
color: var(--muted);
|
|
90
|
+
font-size: 13px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.status {
|
|
94
|
+
color: var(--muted);
|
|
95
|
+
font-size: 13px;
|
|
96
|
+
text-align: right;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.grid {
|
|
100
|
+
display: grid;
|
|
101
|
+
gap: 16px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.top-grid {
|
|
105
|
+
display: grid;
|
|
106
|
+
grid-template-columns: 1.2fr 0.8fr;
|
|
107
|
+
gap: 16px;
|
|
108
|
+
align-items: start;
|
|
109
|
+
margin-bottom: 16px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.secondary-grid {
|
|
113
|
+
display: grid;
|
|
114
|
+
grid-template-columns: 1fr 0.95fr;
|
|
115
|
+
gap: 16px;
|
|
116
|
+
margin-bottom: 16px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.panel {
|
|
120
|
+
border: 1px solid var(--line);
|
|
121
|
+
border-radius: var(--radius);
|
|
122
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
|
|
123
|
+
box-shadow: var(--shadow);
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.panel-head {
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: space-between;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 12px;
|
|
132
|
+
padding: 16px 16px 12px;
|
|
133
|
+
border-bottom: 1px solid var(--line);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.panel-head h2 {
|
|
137
|
+
margin: 0;
|
|
138
|
+
font-size: 14px;
|
|
139
|
+
letter-spacing: 0;
|
|
140
|
+
text-transform: uppercase;
|
|
141
|
+
color: var(--soft);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.panel-body {
|
|
145
|
+
padding: 16px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.goal-title {
|
|
149
|
+
margin: 6px 0 12px;
|
|
150
|
+
font-size: clamp(1.6rem, 2vw, 2.3rem);
|
|
151
|
+
line-height: 1.02;
|
|
152
|
+
max-width: 18ch;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.meta-row {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
gap: 8px;
|
|
159
|
+
margin-bottom: 12px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.pill {
|
|
163
|
+
display: inline-flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 8px;
|
|
166
|
+
padding: 7px 11px;
|
|
167
|
+
border-radius: 999px;
|
|
168
|
+
border: 1px solid var(--line);
|
|
169
|
+
background: rgba(255, 255, 255, 0.035);
|
|
170
|
+
color: var(--soft);
|
|
171
|
+
font-size: 13px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.pill strong {
|
|
175
|
+
color: var(--text);
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pre {
|
|
180
|
+
margin: 0;
|
|
181
|
+
white-space: pre-wrap;
|
|
182
|
+
word-break: break-word;
|
|
183
|
+
color: var(--soft);
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
line-height: 1.6;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.stats {
|
|
189
|
+
display: grid;
|
|
190
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
191
|
+
gap: 12px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.stat {
|
|
195
|
+
padding: 14px;
|
|
196
|
+
border-radius: 12px;
|
|
197
|
+
border: 1px solid var(--line);
|
|
198
|
+
background: rgba(255, 255, 255, 0.03);
|
|
199
|
+
min-height: 94px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.stat .label {
|
|
203
|
+
color: var(--muted);
|
|
204
|
+
font-size: 12px;
|
|
205
|
+
text-transform: uppercase;
|
|
206
|
+
letter-spacing: 0.03em;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.stat .value {
|
|
210
|
+
margin-top: 8px;
|
|
211
|
+
font-size: 1.35rem;
|
|
212
|
+
font-weight: 700;
|
|
213
|
+
line-height: 1.08;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.stat .sub {
|
|
217
|
+
margin-top: 6px;
|
|
218
|
+
color: var(--soft);
|
|
219
|
+
font-size: 13px;
|
|
220
|
+
line-height: 1.4;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.sparkline-wrap {
|
|
224
|
+
display: grid;
|
|
225
|
+
gap: 12px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.chart {
|
|
229
|
+
width: 100%;
|
|
230
|
+
height: 220px;
|
|
231
|
+
border: 1px solid var(--line);
|
|
232
|
+
border-radius: 12px;
|
|
233
|
+
background: rgba(255, 255, 255, 0.025);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.chart-label {
|
|
237
|
+
display: flex;
|
|
238
|
+
justify-content: space-between;
|
|
239
|
+
gap: 12px;
|
|
240
|
+
color: var(--muted);
|
|
241
|
+
font-size: 13px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.table-wrap {
|
|
245
|
+
overflow: auto;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
table {
|
|
249
|
+
width: 100%;
|
|
250
|
+
border-collapse: collapse;
|
|
251
|
+
min-width: 860px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
th,
|
|
255
|
+
td {
|
|
256
|
+
padding: 12px 14px;
|
|
257
|
+
border-bottom: 1px solid var(--line);
|
|
258
|
+
text-align: left;
|
|
259
|
+
vertical-align: top;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
th {
|
|
263
|
+
color: var(--muted);
|
|
264
|
+
font-size: 12px;
|
|
265
|
+
text-transform: uppercase;
|
|
266
|
+
letter-spacing: 0.04em;
|
|
267
|
+
background: rgba(255, 255, 255, 0.02);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
td {
|
|
271
|
+
color: var(--soft);
|
|
272
|
+
font-size: 13px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.best {
|
|
276
|
+
color: var(--accent);
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.worst {
|
|
281
|
+
color: var(--danger);
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.empty {
|
|
286
|
+
color: var(--muted);
|
|
287
|
+
padding: 10px 0;
|
|
288
|
+
font-size: 13px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.footer {
|
|
292
|
+
margin-top: 16px;
|
|
293
|
+
color: var(--muted);
|
|
294
|
+
font-size: 13px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@media (max-width: 1100px) {
|
|
298
|
+
.top-grid,
|
|
299
|
+
.secondary-grid {
|
|
300
|
+
grid-template-columns: 1fr;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
</style>
|
|
304
|
+
</head>
|
|
305
|
+
<body>
|
|
306
|
+
<div class="shell">
|
|
307
|
+
<header>
|
|
308
|
+
<div class="brand">
|
|
309
|
+
<div class="brand-mark">R</div>
|
|
310
|
+
<div>
|
|
311
|
+
<h1>ResearchLoop Dashboard</h1>
|
|
312
|
+
<p>Local experiment tracking for AI research repos.</p>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
<div class="status" id="statusText">Loading state...</div>
|
|
316
|
+
</header>
|
|
317
|
+
|
|
318
|
+
<section class="top-grid">
|
|
319
|
+
<article class="panel">
|
|
320
|
+
<div class="panel-head">
|
|
321
|
+
<h2>Research goal</h2>
|
|
322
|
+
<span class="pill" id="goalMetricPill">Metric: loading</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="panel-body">
|
|
325
|
+
<div class="goal-title" id="goalTitle">Loading…</div>
|
|
326
|
+
<div class="meta-row" id="goalMeta"></div>
|
|
327
|
+
<pre id="goalBody"></pre>
|
|
328
|
+
</div>
|
|
329
|
+
</article>
|
|
330
|
+
|
|
331
|
+
<article class="panel">
|
|
332
|
+
<div class="panel-head">
|
|
333
|
+
<h2>Overview</h2>
|
|
334
|
+
<span class="pill" id="repoPill">Localhost only</span>
|
|
335
|
+
</div>
|
|
336
|
+
<div class="panel-body">
|
|
337
|
+
<div class="stats">
|
|
338
|
+
<div class="stat">
|
|
339
|
+
<div class="label">Runs</div>
|
|
340
|
+
<div class="value" id="statRuns">0</div>
|
|
341
|
+
<div class="sub">All recorded entries in runs.jsonl</div>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="stat">
|
|
344
|
+
<div class="label">Complete</div>
|
|
345
|
+
<div class="value" id="statComplete">0</div>
|
|
346
|
+
<div class="sub">Runs marked complete or completed</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="stat">
|
|
349
|
+
<div class="label">Best</div>
|
|
350
|
+
<div class="value" id="statBest">-</div>
|
|
351
|
+
<div class="sub" id="statBestSub">No metric yet</div>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="stat">
|
|
354
|
+
<div class="label">Latest</div>
|
|
355
|
+
<div class="value" id="statLatest">-</div>
|
|
356
|
+
<div class="sub" id="statLatestSub">Waiting for the first run</div>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</article>
|
|
361
|
+
</section>
|
|
362
|
+
|
|
363
|
+
<section class="secondary-grid">
|
|
364
|
+
<article class="panel">
|
|
365
|
+
<div class="panel-head">
|
|
366
|
+
<h2>Metric trend</h2>
|
|
367
|
+
<span class="pill" id="trendMetric">No metric selected</span>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="panel-body sparkline-wrap">
|
|
370
|
+
<div class="chart-label">
|
|
371
|
+
<span id="trendLabel">No numeric metric yet.</span>
|
|
372
|
+
<span id="trendRange"></span>
|
|
373
|
+
</div>
|
|
374
|
+
<div id="chartMount"></div>
|
|
375
|
+
</div>
|
|
376
|
+
</article>
|
|
377
|
+
|
|
378
|
+
<article class="panel">
|
|
379
|
+
<div class="panel-head">
|
|
380
|
+
<h2>Repo context</h2>
|
|
381
|
+
<span class="pill" id="repoType">Detecting</span>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="panel-body">
|
|
384
|
+
<div class="meta-row" id="repoMeta"></div>
|
|
385
|
+
<pre id="planText"></pre>
|
|
386
|
+
</div>
|
|
387
|
+
</article>
|
|
388
|
+
</section>
|
|
389
|
+
|
|
390
|
+
<section class="panel">
|
|
391
|
+
<div class="panel-head">
|
|
392
|
+
<h2>Runs</h2>
|
|
393
|
+
<span class="pill" id="runHint">Loading</span>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="table-wrap">
|
|
396
|
+
<table>
|
|
397
|
+
<thead>
|
|
398
|
+
<tr>
|
|
399
|
+
<th>Run</th>
|
|
400
|
+
<th>Status</th>
|
|
401
|
+
<th>Primary metric</th>
|
|
402
|
+
<th>Time</th>
|
|
403
|
+
<th>Notes</th>
|
|
404
|
+
</tr>
|
|
405
|
+
</thead>
|
|
406
|
+
<tbody id="runsBody">
|
|
407
|
+
<tr>
|
|
408
|
+
<td colspan="5" class="empty">Loading runs…</td>
|
|
409
|
+
</tr>
|
|
410
|
+
</tbody>
|
|
411
|
+
</table>
|
|
412
|
+
</div>
|
|
413
|
+
</section>
|
|
414
|
+
|
|
415
|
+
<div class="footer">
|
|
416
|
+
No auth. No accounts. Served from localhost and driven by `.researchloop/`.
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<script>
|
|
421
|
+
const stateUrl = "/api/state";
|
|
422
|
+
let latestState = null;
|
|
423
|
+
|
|
424
|
+
function escapeHtml(value) {
|
|
425
|
+
return String(value ?? "")
|
|
426
|
+
.replaceAll("&", "&")
|
|
427
|
+
.replaceAll("<", "<")
|
|
428
|
+
.replaceAll(">", ">")
|
|
429
|
+
.replaceAll('"', """)
|
|
430
|
+
.replaceAll("'", "'");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function formatTime(value) {
|
|
434
|
+
if (!value) return "-";
|
|
435
|
+
const date = new Date(value);
|
|
436
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
437
|
+
return date.toLocaleString();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function metricFromRun(run, key) {
|
|
441
|
+
if (!run || !run.metrics || key == null) return "-";
|
|
442
|
+
const value = run.metrics[key];
|
|
443
|
+
return Number.isFinite(Number(value)) ? Number(value).toFixed(4) : String(value);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function renderPills(items) {
|
|
447
|
+
return items
|
|
448
|
+
.filter(Boolean)
|
|
449
|
+
.map((item) => `<span class="pill">${item}</span>`)
|
|
450
|
+
.join("");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function sparkline(series, preferHigher) {
|
|
454
|
+
if (!series || !series.length) {
|
|
455
|
+
return '<div class="empty">No numeric metric data yet.</div>';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const width = 860;
|
|
459
|
+
const height = 220;
|
|
460
|
+
const pad = 20;
|
|
461
|
+
const values = series.map((point) => Number(point.value));
|
|
462
|
+
const min = Math.min(...values);
|
|
463
|
+
const max = Math.max(...values);
|
|
464
|
+
const range = max - min || 1;
|
|
465
|
+
const points = values
|
|
466
|
+
.map((value, index) => {
|
|
467
|
+
const x = pad + ((width - pad * 2) * (series.length === 1 ? 0.5 : index / (series.length - 1)));
|
|
468
|
+
const y = height - pad - ((value - min) / range) * (height - pad * 2);
|
|
469
|
+
return `${x},${y}`;
|
|
470
|
+
})
|
|
471
|
+
.join(" ");
|
|
472
|
+
|
|
473
|
+
const marker = preferHigher ? "higher is better" : "lower is better";
|
|
474
|
+
const last = series[series.length - 1];
|
|
475
|
+
return `
|
|
476
|
+
<svg class="chart" viewBox="0 0 ${width} ${height}" role="img" aria-label="Metric sparkline">
|
|
477
|
+
<defs>
|
|
478
|
+
<linearGradient id="lineGradient" x1="0" y1="0" x2="1" y2="0">
|
|
479
|
+
<stop offset="0%" stop-color="#62d6a6" />
|
|
480
|
+
<stop offset="100%" stop-color="#71a7ff" />
|
|
481
|
+
</linearGradient>
|
|
482
|
+
</defs>
|
|
483
|
+
<line x1="${pad}" y1="${height - pad}" x2="${width - pad}" y2="${height - pad}" stroke="rgba(255,255,255,0.08)" />
|
|
484
|
+
<line x1="${pad}" y1="${pad}" x2="${pad}" y2="${height - pad}" stroke="rgba(255,255,255,0.08)" />
|
|
485
|
+
<polyline points="${points}" fill="none" stroke="url(#lineGradient)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
|
|
486
|
+
<circle cx="${width - pad}" cy="${height - pad - ((Number(last.value) - min) / range) * (height - pad * 2)}" r="5" fill="#62d6a6" />
|
|
487
|
+
<text x="${pad}" y="${pad - 4}" fill="rgba(255,255,255,0.55)" font-size="12">${marker}</text>
|
|
488
|
+
</svg>
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function renderRuns(state) {
|
|
493
|
+
const metricKey = state.primaryMetric || "";
|
|
494
|
+
const bestId = state.summary?.bestRun?.run?.id;
|
|
495
|
+
const worstId = state.summary?.worstRun?.run?.id;
|
|
496
|
+
const rows = (state.runs || []).filter((run) => !run.parse_error);
|
|
497
|
+
|
|
498
|
+
if (!rows.length) {
|
|
499
|
+
document.getElementById("runsBody").innerHTML =
|
|
500
|
+
'<tr><td colspan="5" class="empty">No runs recorded yet. Use <code>researchloop record</code> after the next experiment.</td></tr>';
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const html = rows
|
|
505
|
+
.slice()
|
|
506
|
+
.reverse()
|
|
507
|
+
.map((run) => {
|
|
508
|
+
const isBest = bestId && run.id === bestId;
|
|
509
|
+
const isWorst = worstId && run.id === worstId;
|
|
510
|
+
const metricValue = metricFromRun(run, metricKey);
|
|
511
|
+
const metricClass = isBest ? "best" : isWorst ? "worst" : "";
|
|
512
|
+
const metrics = run.metrics || {};
|
|
513
|
+
const metricSummary = metricKey
|
|
514
|
+
? `${metricKey}: ${metricValue}`
|
|
515
|
+
: Object.entries(metrics)
|
|
516
|
+
.slice(0, 3)
|
|
517
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
518
|
+
.join(", ");
|
|
519
|
+
const note = run.notes ? escapeHtml(run.notes) : "-";
|
|
520
|
+
return `
|
|
521
|
+
<tr>
|
|
522
|
+
<td><strong>${escapeHtml(run.id || "-")}</strong></td>
|
|
523
|
+
<td>${escapeHtml(run.status || "-")}</td>
|
|
524
|
+
<td class="${metricClass}">${escapeHtml(metricSummary || "-")}</td>
|
|
525
|
+
<td>${escapeHtml(formatTime(run.timestamp))}</td>
|
|
526
|
+
<td>${note}</td>
|
|
527
|
+
</tr>
|
|
528
|
+
`;
|
|
529
|
+
})
|
|
530
|
+
.join("");
|
|
531
|
+
|
|
532
|
+
document.getElementById("runsBody").innerHTML = html;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function render(state) {
|
|
536
|
+
latestState = state;
|
|
537
|
+
const goal = state.goal || {};
|
|
538
|
+
const summary = state.summary || {};
|
|
539
|
+
const repoProfile = state.repoProfile || {};
|
|
540
|
+
const plan = state.plan || {};
|
|
541
|
+
|
|
542
|
+
document.getElementById("statusText").textContent =
|
|
543
|
+
`Updated ${formatTime(state.generatedAt)} · ${summary.totalRuns || 0} runs`;
|
|
544
|
+
document.getElementById("repoPill").textContent = repoProfile.cwd ? "Repo loaded" : "Repo unknown";
|
|
545
|
+
document.getElementById("repoType").textContent = Array.isArray(repoProfile.adapters)
|
|
546
|
+
? repoProfile.adapters.join(", ")
|
|
547
|
+
: "generic";
|
|
548
|
+
|
|
549
|
+
document.getElementById("goalTitle").textContent = goal.goal || "Set a research goal with `researchloop goal`.";
|
|
550
|
+
document.getElementById("goalBody").textContent = goal.raw || "No goal file found yet.";
|
|
551
|
+
document.getElementById("goalMetricPill").textContent = goal.metric
|
|
552
|
+
? `Metric: ${goal.metric}`
|
|
553
|
+
: "Metric: not set";
|
|
554
|
+
|
|
555
|
+
const goalMeta = [];
|
|
556
|
+
if (goal.direction) goalMeta.push(`<span class="pill">Direction: <strong>${escapeHtml(goal.direction)}</strong></span>`);
|
|
557
|
+
if (goal.baseline) goalMeta.push(`<span class="pill">Baseline: <strong>${escapeHtml(goal.baseline)}</strong></span>`);
|
|
558
|
+
if (goal.evaluation) goalMeta.push(`<span class="pill">Eval: <strong>${escapeHtml(goal.evaluation)}</strong></span>`);
|
|
559
|
+
document.getElementById("goalMeta").innerHTML = renderPills(goalMeta);
|
|
560
|
+
|
|
561
|
+
document.getElementById("statRuns").textContent = summary.totalRuns || 0;
|
|
562
|
+
document.getElementById("statComplete").textContent = summary.completeRuns || 0;
|
|
563
|
+
|
|
564
|
+
if (summary.bestRun) {
|
|
565
|
+
document.getElementById("statBest").textContent = metricKeyLabel(state.primaryMetric, summary.bestRun.value);
|
|
566
|
+
document.getElementById("statBestSub").textContent = `${summary.bestRun.run.id} · ${formatTime(summary.bestRun.run.timestamp)}`;
|
|
567
|
+
} else {
|
|
568
|
+
document.getElementById("statBest").textContent = "-";
|
|
569
|
+
document.getElementById("statBestSub").textContent = "No metric data yet";
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (summary.latestRun) {
|
|
573
|
+
document.getElementById("statLatest").textContent = summary.latestRun.id || "-";
|
|
574
|
+
document.getElementById("statLatestSub").textContent = `${summary.latestRun.status || "-"} · ${formatTime(summary.latestRun.timestamp)}`;
|
|
575
|
+
} else {
|
|
576
|
+
document.getElementById("statLatest").textContent = "-";
|
|
577
|
+
document.getElementById("statLatestSub").textContent = "Waiting for the first run";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
document.getElementById("planText").textContent = plan.raw || "No plan file yet.";
|
|
581
|
+
document.getElementById("repoMeta").innerHTML = renderPills([
|
|
582
|
+
repoProfile.cwd ? `<strong>Repo:</strong> ${escapeHtml(repoProfile.cwd)}` : "",
|
|
583
|
+
Array.isArray(repoProfile.package_files) && repoProfile.package_files.length
|
|
584
|
+
? `<strong>Packages:</strong> ${escapeHtml(repoProfile.package_files.join(", "))}`
|
|
585
|
+
: "",
|
|
586
|
+
repoProfile.git_branch ? `<strong>Branch:</strong> ${escapeHtml(repoProfile.git_branch)}` : "",
|
|
587
|
+
]);
|
|
588
|
+
|
|
589
|
+
document.getElementById("trendMetric").textContent = state.primaryMetric
|
|
590
|
+
? `Metric: ${state.primaryMetric}`
|
|
591
|
+
: "No metric selected";
|
|
592
|
+
document.getElementById("trendLabel").textContent = state.primaryMetric
|
|
593
|
+
? `Tracking ${state.primaryMetric} across recorded runs.`
|
|
594
|
+
: "No numeric metric found yet.";
|
|
595
|
+
document.getElementById("trendRange").textContent = summary.series && summary.series.length
|
|
596
|
+
? `${summary.series.length} points`
|
|
597
|
+
: "";
|
|
598
|
+
document.getElementById("chartMount").innerHTML = sparkline(summary.series, state.preferHigher);
|
|
599
|
+
|
|
600
|
+
document.getElementById("runHint").textContent = summary.parseErrors
|
|
601
|
+
? `${summary.parseErrors} parse errors`
|
|
602
|
+
: `${summary.totalRuns || 0} entries`;
|
|
603
|
+
|
|
604
|
+
renderRuns(state);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function metricKeyLabel(metricKey, value) {
|
|
608
|
+
if (!metricKey) return "-";
|
|
609
|
+
return `${metricKey}: ${Number(value).toFixed(4)}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function refresh() {
|
|
613
|
+
try {
|
|
614
|
+
const res = await fetch(stateUrl, { cache: "no-store" });
|
|
615
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
616
|
+
const state = await res.json();
|
|
617
|
+
render(state);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
document.getElementById("statusText").textContent = `Failed to load state: ${error.message}`;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
refresh();
|
|
624
|
+
setInterval(refresh, 3000);
|
|
625
|
+
</script>
|
|
626
|
+
</body>
|
|
627
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
You are Claude Code acting as an autonomous research engineer in this repository.
|
|
2
|
+
|
|
3
|
+
First read:
|
|
4
|
+
- `.researchloop/AGENTS.md`
|
|
5
|
+
- `.researchloop/goal.md`
|
|
6
|
+
- `.researchloop/plan.md`
|
|
7
|
+
- `.researchloop/scratchpad/THREAD.md`
|
|
8
|
+
- `.researchloop/repo-profile.json` if present
|
|
9
|
+
|
|
10
|
+
Goal:
|
|
11
|
+
{{GOAL}}
|
|
12
|
+
|
|
13
|
+
Autonomy rule:
|
|
14
|
+
Do not stop just because one experiment is exhausted. If the current idea fails, log the result, update the picklist, and start the next smallest useful experiment. If you are unsure between two reasonable directions, pick one and continue.
|
|
15
|
+
|
|
16
|
+
Operating rules:
|
|
17
|
+
- Establish or recover the baseline first.
|
|
18
|
+
- Keep the experiment surface constrained.
|
|
19
|
+
- Define a kill criterion before sweeps.
|
|
20
|
+
- Record commands and metrics.
|
|
21
|
+
- Reproduce plausible wins before treating them as real.
|
|
22
|
+
- Run pruning checks before promoting a stacked recipe.
|
|
23
|
+
- Keep `.researchloop/plan.md` current.
|
|
24
|
+
- Keep `.researchloop/scratchpad/THREAD.md` append-only.
|
|
25
|
+
|
|
26
|
+
Return with:
|
|
27
|
+
- Current state
|
|
28
|
+
- Evidence
|
|
29
|
+
- Result
|
|
30
|
+
- Next autonomous action
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
You are Codex acting as an autonomous research engineer in this repository.
|
|
2
|
+
|
|
3
|
+
First read:
|
|
4
|
+
- `.researchloop/AGENTS.md`
|
|
5
|
+
- `.researchloop/goal.md`
|
|
6
|
+
- `.researchloop/plan.md`
|
|
7
|
+
- `.researchloop/scratchpad/THREAD.md`
|
|
8
|
+
- `.researchloop/repo-profile.json` if present
|
|
9
|
+
|
|
10
|
+
Goal:
|
|
11
|
+
{{GOAL}}
|
|
12
|
+
|
|
13
|
+
Operating rules:
|
|
14
|
+
- Inspect before editing.
|
|
15
|
+
- Establish the baseline command and metric before optimizing.
|
|
16
|
+
- Prefer the smallest experiment that can create signal.
|
|
17
|
+
- Write idea notes for non-trivial changes.
|
|
18
|
+
- Run focused checks after every meaningful change.
|
|
19
|
+
- Append every meaningful event to `.researchloop/scratchpad/THREAD.md`.
|
|
20
|
+
- Append every run to `.researchloop/scratchpad/runs.jsonl`.
|
|
21
|
+
- Never claim a result you did not run.
|
|
22
|
+
- If a path fails, log the lesson and choose the next experiment.
|
|
23
|
+
|
|
24
|
+
Return with:
|
|
25
|
+
- Current state
|
|
26
|
+
- Files changed
|
|
27
|
+
- Commands run
|
|
28
|
+
- Results
|
|
29
|
+
- Next experiment
|