headlesscode 1.0.2 → 1.2.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/README.md +100 -56
- package/package.json +1 -1
- package/src/cli.ts +115 -4
- package/src/engine/claims.ts +503 -0
- package/src/engine/events.ts +32 -0
- package/src/engine/lazy-tools.ts +30 -9
- package/src/engine/loop.ts +462 -7
- package/src/engine/prompt.ts +5 -1
- package/src/engine/types.ts +36 -0
- package/src/rsi/archive.ts +129 -0
- package/src/rsi/config.ts +312 -0
- package/src/rsi/controller.ts +268 -0
- package/src/rsi/curriculum.ts +68 -0
- package/src/rsi/evaluator.ts +106 -0
- package/src/rsi/fitness.ts +64 -0
- package/src/rsi/index.ts +16 -0
- package/src/rsi/models.ts +89 -0
- package/src/rsi/mutation.ts +77 -0
- package/src/rsi/reports.ts +47 -0
- package/src/rsi/roles.ts +37 -0
- package/src/rsi/sandbox.ts +10 -0
- package/src/rsi/search.ts +32 -0
- package/src/rsi/selection.ts +132 -0
- package/src/rsi/trajectory.ts +143 -0
- package/src/rsi/types.ts +317 -0
- package/src/rsi/workspace.ts +96 -0
- package/src/tools/executor.ts +232 -7
package/src/rsi/types.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process"
|
|
2
|
+
|
|
3
|
+
export type CandidateStatus = "planned" | "mutating" | "evaluating" | "accepted" | "rejected" | "failed"
|
|
4
|
+
export type MutationKind = "corrective" | "architectural" | "search-policy" | "curriculum" | "model-adaptation"
|
|
5
|
+
export type ParentSelectionPolicy = "champion-specialist-novelty" | "pareto-front" | "all-eligible"
|
|
6
|
+
export type ComputePolicy = "single" | "independent" | "planner-executors" | "critic-retry"
|
|
7
|
+
|
|
8
|
+
export interface MutationHypothesis {
|
|
9
|
+
statement: string
|
|
10
|
+
expectedEffect: string
|
|
11
|
+
potentialDownside: string
|
|
12
|
+
evidence?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MetricVector {
|
|
16
|
+
correctness: number
|
|
17
|
+
reliability: number
|
|
18
|
+
generalization: number
|
|
19
|
+
hidden: number
|
|
20
|
+
efficiency: number
|
|
21
|
+
latency: number
|
|
22
|
+
tokenUse: number
|
|
23
|
+
recovery: number
|
|
24
|
+
fabricationRate: number
|
|
25
|
+
complexityPenalty: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ComplexityMetrics {
|
|
29
|
+
diffLines: number
|
|
30
|
+
changedFiles: number
|
|
31
|
+
newDependencies: number
|
|
32
|
+
additionalModelCalls: number
|
|
33
|
+
runtimeOverheadMs: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ParentSelectionReason {
|
|
37
|
+
strategy: "champion" | "specialist" | "novelty" | "pareto" | "archive" | "baseline"
|
|
38
|
+
reason: string
|
|
39
|
+
metrics?: Partial<MetricVector>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ModelRole =
|
|
43
|
+
| "worker"
|
|
44
|
+
| "mutation-architect"
|
|
45
|
+
| "failure-analyst"
|
|
46
|
+
| "critic"
|
|
47
|
+
| "adversary"
|
|
48
|
+
| "reviewer"
|
|
49
|
+
| "curriculum-designer"
|
|
50
|
+
| "training-data-curator"
|
|
51
|
+
|
|
52
|
+
export interface RoleModelConfig {
|
|
53
|
+
provider: "ollama" | "openrouter" | "command"
|
|
54
|
+
model: string
|
|
55
|
+
baseUrl?: string
|
|
56
|
+
command?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type RsiRoleConfig = Partial<Record<ModelRole, RoleModelConfig>>
|
|
60
|
+
|
|
61
|
+
export interface TrainingConfig {
|
|
62
|
+
method: "none" | "lora" | "qlora" | "sft" | "preference"
|
|
63
|
+
command?: string
|
|
64
|
+
seed?: number
|
|
65
|
+
parameters?: Record<string, string | number | boolean>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ModelCandidate {
|
|
69
|
+
id: string
|
|
70
|
+
parentModel: string
|
|
71
|
+
trainingMethod: TrainingConfig["method"]
|
|
72
|
+
datasetVersion?: string
|
|
73
|
+
trainingConfig: TrainingConfig
|
|
74
|
+
artifactPath?: string
|
|
75
|
+
artifactHash?: string
|
|
76
|
+
status: "base" | "prepared" | "trained" | "evaluated" | "accepted" | "rejected"
|
|
77
|
+
createdAt: string
|
|
78
|
+
provenance?: Record<string, string>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface HarnessModelCombination {
|
|
82
|
+
id: string
|
|
83
|
+
harnessCandidateId: string
|
|
84
|
+
modelCandidateId: string
|
|
85
|
+
status: "planned" | "evaluated" | "accepted" | "rejected"
|
|
86
|
+
metrics?: MetricVector
|
|
87
|
+
fitness?: Fitness
|
|
88
|
+
createdAt: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type ResourceClass = "LOCAL_GPU" | "CPU" | "REMOTE_API" | "TRAINING_GPU"
|
|
92
|
+
export type ExperimentJobKind = "mutation" | "evaluation" | "adversarial" | "curriculum" | "training" | "model-evaluation" | "critic"
|
|
93
|
+
export type ExperimentJobStatus = "queued" | "running" | "completed" | "failed" | "cancelled"
|
|
94
|
+
|
|
95
|
+
export interface ResourceRequirements {
|
|
96
|
+
class: ResourceClass
|
|
97
|
+
units: number
|
|
98
|
+
concurrencyKey?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface ExperimentJob {
|
|
102
|
+
id: string
|
|
103
|
+
kind: ExperimentJobKind
|
|
104
|
+
status: ExperimentJobStatus
|
|
105
|
+
resource: ResourceRequirements
|
|
106
|
+
owner?: string
|
|
107
|
+
candidateId?: string
|
|
108
|
+
createdAt: string
|
|
109
|
+
updatedAt: string
|
|
110
|
+
attempts: number
|
|
111
|
+
error?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface TrajectoryMessage {
|
|
115
|
+
role: string
|
|
116
|
+
content?: string
|
|
117
|
+
tool_calls?: unknown[]
|
|
118
|
+
tool_call_id?: string
|
|
119
|
+
name?: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TrajectoryRecord {
|
|
123
|
+
id: string
|
|
124
|
+
task: string
|
|
125
|
+
environment: { repoRoot: string; baseCommit: string; generation: number }
|
|
126
|
+
model: { id: string; provider?: string; candidateId?: string }
|
|
127
|
+
harnessVersion: string
|
|
128
|
+
promptConfig: Record<string, unknown>
|
|
129
|
+
messages: TrajectoryMessage[]
|
|
130
|
+
toolCalls: number
|
|
131
|
+
outcome: "success" | "failure" | "incomplete"
|
|
132
|
+
verification: { verified: boolean; regressionPass: boolean; hiddenPass: boolean }
|
|
133
|
+
failureClassification?: string
|
|
134
|
+
criticDiagnosis?: string
|
|
135
|
+
fitnessImpact?: number
|
|
136
|
+
provenance: { source: string; capturedAt: string; trusted: boolean }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface TrajectorySummary {
|
|
140
|
+
path: string
|
|
141
|
+
messageCount: number
|
|
142
|
+
toolCalls: number
|
|
143
|
+
trusted: boolean
|
|
144
|
+
outcome: TrajectoryRecord["outcome"]
|
|
145
|
+
failureClassification?: string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface CurriculumTask {
|
|
149
|
+
id: string
|
|
150
|
+
task: string
|
|
151
|
+
difficulty: 1 | 2 | 3 | 4 | 5
|
|
152
|
+
capability: string
|
|
153
|
+
groundTruthCommand: string
|
|
154
|
+
provenance: { sourceCandidateIds: string[]; failureClass: string; generatedAt: string }
|
|
155
|
+
validated: boolean
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface RsiConfig {
|
|
159
|
+
repoRoot: string
|
|
160
|
+
model: string
|
|
161
|
+
population: number
|
|
162
|
+
generations: number
|
|
163
|
+
maxConcurrent: number
|
|
164
|
+
mutationTask: string
|
|
165
|
+
evalCommands: string[]
|
|
166
|
+
hiddenEvalCommands: string[]
|
|
167
|
+
archiveDir: string
|
|
168
|
+
worktreeDir: string
|
|
169
|
+
baseRef: string
|
|
170
|
+
seed: string
|
|
171
|
+
dryRun: boolean
|
|
172
|
+
keepWorktrees: boolean
|
|
173
|
+
maxIterations: number
|
|
174
|
+
protectedPaths: string[]
|
|
175
|
+
commandTimeoutMs: number
|
|
176
|
+
parentSelectionPolicy?: ParentSelectionPolicy
|
|
177
|
+
eliteCount?: number
|
|
178
|
+
specialistCount?: number
|
|
179
|
+
noveltyRate?: number
|
|
180
|
+
mutationKind?: MutationKind
|
|
181
|
+
hypothesis?: MutationHypothesis
|
|
182
|
+
computePolicy?: ComputePolicy
|
|
183
|
+
modelCandidateId?: string
|
|
184
|
+
modelCandidates?: ModelCandidate[]
|
|
185
|
+
roles?: RsiRoleConfig
|
|
186
|
+
trajectoryDir?: string
|
|
187
|
+
curriculumDir?: string
|
|
188
|
+
resumeRunId?: string
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface CandidateRecord {
|
|
192
|
+
id: string
|
|
193
|
+
generation: number
|
|
194
|
+
parent: string
|
|
195
|
+
branch: string
|
|
196
|
+
worktree: string
|
|
197
|
+
baseCommit: string
|
|
198
|
+
status: CandidateStatus
|
|
199
|
+
model: string
|
|
200
|
+
mutation: string
|
|
201
|
+
createdAt: string
|
|
202
|
+
updatedAt: string
|
|
203
|
+
commits: string[]
|
|
204
|
+
changedFiles: string[]
|
|
205
|
+
protectedPathViolations: string[]
|
|
206
|
+
mutationKind?: MutationKind
|
|
207
|
+
hypothesis?: MutationHypothesis
|
|
208
|
+
parentSelection?: ParentSelectionReason
|
|
209
|
+
parentCommit?: string
|
|
210
|
+
modelCandidateId?: string
|
|
211
|
+
combinationId?: string
|
|
212
|
+
failure?: string
|
|
213
|
+
result?: TrialResult
|
|
214
|
+
fitness?: Fitness
|
|
215
|
+
trajectory?: TrajectorySummary
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface TrialResult {
|
|
219
|
+
ok: boolean
|
|
220
|
+
command: string
|
|
221
|
+
exitCode: number | null
|
|
222
|
+
durationMs: number
|
|
223
|
+
stdout: string
|
|
224
|
+
stderr: string
|
|
225
|
+
timedOut?: boolean
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface EvaluationSummary {
|
|
229
|
+
regression: TrialResult
|
|
230
|
+
visible: TrialResult[]
|
|
231
|
+
hidden: TrialResult[]
|
|
232
|
+
completed: boolean
|
|
233
|
+
crashed: boolean
|
|
234
|
+
protectedPathViolation: boolean
|
|
235
|
+
changedFiles: string[]
|
|
236
|
+
committed?: boolean
|
|
237
|
+
complexity?: ComplexityMetrics
|
|
238
|
+
failureClassification?: string
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface HardGates {
|
|
242
|
+
regressionPass: boolean
|
|
243
|
+
visibleEvalPass: boolean
|
|
244
|
+
hiddenEvalPass: boolean
|
|
245
|
+
noProtectedPathViolation: boolean
|
|
246
|
+
completed: boolean
|
|
247
|
+
noCrash: boolean
|
|
248
|
+
committed: boolean
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface Fitness {
|
|
252
|
+
score: number
|
|
253
|
+
components: {
|
|
254
|
+
regression: number
|
|
255
|
+
visible: number
|
|
256
|
+
hidden: number
|
|
257
|
+
efficiency: number
|
|
258
|
+
recovery: number
|
|
259
|
+
}
|
|
260
|
+
metrics: MetricVector
|
|
261
|
+
complexity: ComplexityMetrics
|
|
262
|
+
paretoRank?: number
|
|
263
|
+
hardGates: HardGates
|
|
264
|
+
reason: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface RsiRunRecord {
|
|
268
|
+
runId: string
|
|
269
|
+
startedAt: string
|
|
270
|
+
finishedAt?: string
|
|
271
|
+
baseline?: TrialResult
|
|
272
|
+
model: string
|
|
273
|
+
baseRef: string
|
|
274
|
+
baseCommit: string
|
|
275
|
+
generations: number
|
|
276
|
+
selected?: string
|
|
277
|
+
selectedCandidates?: string[]
|
|
278
|
+
parentSelectionPolicy?: ParentSelectionPolicy
|
|
279
|
+
parentChoices?: Record<string, ParentSelectionReason>
|
|
280
|
+
modelCandidates?: ModelCandidate[]
|
|
281
|
+
combinations?: HarnessModelCombination[]
|
|
282
|
+
jobs?: ExperimentJob[]
|
|
283
|
+
trajectoryRefs?: string[]
|
|
284
|
+
curriculumTasks?: CurriculumTask[]
|
|
285
|
+
candidates: CandidateRecord[]
|
|
286
|
+
reports: string[]
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface RsiArchive {
|
|
290
|
+
schemaVersion: 2
|
|
291
|
+
updatedAt: string
|
|
292
|
+
runs: RsiRunRecord[]
|
|
293
|
+
activeRuns: RsiRunRecord[]
|
|
294
|
+
candidates: CandidateRecord[]
|
|
295
|
+
modelCandidates: ModelCandidate[]
|
|
296
|
+
combinations: HarnessModelCombination[]
|
|
297
|
+
jobs: ExperimentJob[]
|
|
298
|
+
trajectoryRefs: string[]
|
|
299
|
+
curriculumTasks: CurriculumTask[]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface CommandRunner {
|
|
303
|
+
(command: string, cwd: string, timeoutMs: number, env?: NodeJS.ProcessEnv): Promise<TrialResult>
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface MutationRunner {
|
|
307
|
+
(candidate: CandidateRecord, config: RsiConfig): Promise<{ ok: boolean; result?: TrialResult; error?: string }>
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface RsiHooks {
|
|
311
|
+
runCommand?: CommandRunner
|
|
312
|
+
runMutation?: MutationRunner
|
|
313
|
+
now?: () => string
|
|
314
|
+
log?: (line: string) => void
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export type SpawnedProcess = ChildProcess
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process"
|
|
2
|
+
import * as fs from "node:fs/promises"
|
|
3
|
+
import * as path from "node:path"
|
|
4
|
+
import { promisify } from "node:util"
|
|
5
|
+
import { modelCombinationId } from "./models.js"
|
|
6
|
+
import type { CandidateRecord, RsiConfig } from "./types.js"
|
|
7
|
+
import type { ParentChoice } from "./selection.js"
|
|
8
|
+
|
|
9
|
+
const execFile = promisify(execFileCallback)
|
|
10
|
+
|
|
11
|
+
export function candidateId(generation: number, index: number, seed: string): string {
|
|
12
|
+
const cleanSeed = seed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "run"
|
|
13
|
+
return `g${generation}-c${String(index + 1).padStart(2, "0")}-${cleanSeed.slice(0, 20)}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function candidateBranch(id: string): string {
|
|
17
|
+
return `rsi/${id}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function candidateWorktree(config: RsiConfig, id: string): string {
|
|
21
|
+
return path.join(config.worktreeDir, id)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function gitOutput(repoRoot: string, args: string[]): Promise<string> {
|
|
25
|
+
const result = await execFile("git", args, { cwd: repoRoot, maxBuffer: 4 * 1024 * 1024 })
|
|
26
|
+
return result.stdout.trim()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function resolveBaseCommit(config: RsiConfig): Promise<string> {
|
|
30
|
+
return gitOutput(config.repoRoot, ["rev-parse", `${config.baseRef}^{commit}`])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function newCandidate(
|
|
34
|
+
config: RsiConfig,
|
|
35
|
+
generation: number,
|
|
36
|
+
index: number,
|
|
37
|
+
baseCommit: string,
|
|
38
|
+
now: string,
|
|
39
|
+
parentChoice?: ParentChoice,
|
|
40
|
+
): CandidateRecord {
|
|
41
|
+
const id = candidateId(generation, index, config.seed)
|
|
42
|
+
const modelCandidateId = config.modelCandidateId ?? "model-base"
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
generation,
|
|
46
|
+
parent: parentChoice?.candidateId ?? (generation === 0 ? "baseline" : "champion"),
|
|
47
|
+
branch: candidateBranch(id),
|
|
48
|
+
worktree: candidateWorktree(config, id),
|
|
49
|
+
baseCommit,
|
|
50
|
+
status: "planned",
|
|
51
|
+
model: config.model,
|
|
52
|
+
mutation: config.mutationTask,
|
|
53
|
+
createdAt: now,
|
|
54
|
+
updatedAt: now,
|
|
55
|
+
commits: [],
|
|
56
|
+
changedFiles: [],
|
|
57
|
+
protectedPathViolations: [],
|
|
58
|
+
mutationKind: config.mutationKind ?? "corrective",
|
|
59
|
+
hypothesis: config.hypothesis,
|
|
60
|
+
parentSelection: parentChoice?.reason ?? (generation === 0 ? { strategy: "baseline", reason: "initial base ref" } : undefined),
|
|
61
|
+
parentCommit: parentChoice?.baseCommit ?? baseCommit,
|
|
62
|
+
modelCandidateId,
|
|
63
|
+
combinationId: modelCombinationId(id, modelCandidateId),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function createCandidateWorktree(config: RsiConfig, candidate: CandidateRecord): Promise<void> {
|
|
68
|
+
await fs.mkdir(config.worktreeDir, { recursive: true })
|
|
69
|
+
await execFile("git", ["worktree", "add", "-b", candidate.branch, candidate.worktree, candidate.baseCommit], {
|
|
70
|
+
cwd: config.repoRoot,
|
|
71
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function removeCandidateWorktree(config: RsiConfig, candidate: CandidateRecord): Promise<void> {
|
|
76
|
+
await execFile("git", ["worktree", "remove", "--force", candidate.worktree], {
|
|
77
|
+
cwd: config.repoRoot,
|
|
78
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function changedFiles(repoRoot: string, baseCommit: string): Promise<string[]> {
|
|
83
|
+
const output = await gitOutput(repoRoot, ["diff", "--name-only", `${baseCommit}...HEAD`])
|
|
84
|
+
const status = await gitOutput(repoRoot, ["status", "--short"])
|
|
85
|
+
const files = new Set(output.split("\n").map((file) => file.trim()).filter(Boolean))
|
|
86
|
+
for (const line of status.split("\n")) {
|
|
87
|
+
const file = line.slice(3).trim()
|
|
88
|
+
if (file) files.add(file)
|
|
89
|
+
}
|
|
90
|
+
return [...files].sort()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function candidateCommits(repoRoot: string, baseCommit: string): Promise<string[]> {
|
|
94
|
+
const output = await gitOutput(repoRoot, ["log", "--format=%H", `${baseCommit}..HEAD`])
|
|
95
|
+
return output.split("\n").map((commit) => commit.trim()).filter(Boolean)
|
|
96
|
+
}
|