Overview
The Reconciler runs periodic sweeps to check repository health (build, tests, conflict markers) and generates fix tasks when issues are detected. It uses adaptive sweep intervals based on health status.
Location: packages/orchestrator/src/reconciler.ts
Class: Reconciler
Constructor
new Reconciler(
config: OrchestratorConfig,
reconcilerConfig: ReconcilerConfig,
taskQueue: TaskQueue,
mergeQueue: MergeQueue,
monitor: Monitor,
systemPrompt: string,
deps?: Partial<ReconcilerDeps>
)
Configuration for sweep interval and max fix tasks
Loaded from prompts/reconciler.md
Configuration
ReconcilerConfig
interface ReconcilerConfig {
intervalMs: number // Sweep interval (default: 300_000 = 5 min)
maxFixTasks: number // Max fix tasks per sweep (default: 5)
}
Default Configuration:
const DEFAULT_RECONCILER_CONFIG: ReconcilerConfig = {
intervalMs: 300_000,
maxFixTasks: 5
}
Core Methods
start()
Starts the periodic sweep timer.
Runs sweep() every intervalMs until stopped.
stop()
Stops the periodic sweep timer.
sweep()
Runs a single health check sweep.
async sweep(): Promise<SweepResult>
Returns: SweepResult
interface SweepResult {
buildOk: boolean // tsc --noEmit passed
testsOk: boolean // npm test passed
hasConflictMarkers: boolean // <<<<<<< markers detected
buildOutput: string // Build error output (truncated to 8000 chars)
testOutput: string // Test error output (truncated to 8000 chars)
conflictFiles: string[] // Files with conflict markers
fixTasks: Task[] // Generated fix tasks
}
Sweep Phases:
- Type Check:
npx tsc --noEmit
- Build:
npm run build --if-present
- Test:
npm test
- Conflict Scan:
git grep -rl "<<<<<<<" -- *.ts *.tsx *.js *.json
- Staleness Check: Skip if merges occurred during sweep
- LLM Fix Generation: If issues found, prompt LLM for fix tasks
Health Checks
Build Check
const tscResult = await runCommand('npx', ['tsc', '--noEmit'], repoPath)
const buildOutput = tscResult.stdout + tscResult.stderr
const buildNotConfigured = /no inputs were found|could not find a valid tsconfig/i.test(buildOutput)
const buildOk = buildNotConfigured || (tscResult.code === 0 && !tscResult.stderr?.includes('error TS'))
Considered OK if:
- TypeScript not configured (no tsconfig.json)
- Exit code 0 and no “error TS” in stderr
Test Check
const testResult = await runCommand('npm', ['test'], repoPath)
const testOutput = testResult.stdout + testResult.stderr
const testNotConfigured = /Missing script|no test specified/i.test(testOutput)
const testsOk = testNotConfigured || (testResult.code === 0 && !testResult.stderr?.includes('FAIL'))
Considered OK if:
- No test script configured
- Exit code 0 and no “FAIL” in stderr
Conflict Marker Detection
const conflictResult = await runCommand(
'git',
['grep', '-rl', '<<<<<<<', '--', '*.ts', '*.tsx', '*.js', '*.json'],
repoPath
)
const conflictFiles = conflictResult.stdout.trim().split('\n').filter(Boolean)
const hasConflictMarkers = conflictFiles.length > 0
Searches for <<<<<<< markers in source files.
Adaptive Sweep Interval
Green Sweep Tracking
private consecutiveGreenSweeps: number = 0
private currentIntervalMs: number
private readonly minIntervalMs: number // 60 seconds
private readonly maxIntervalMs: number // 300 seconds (5 min)
Behavior:
if (buildOk && testsOk && !hasConflictMarkers) {
this.consecutiveGreenSweeps++
if (this.consecutiveGreenSweeps >= 3) {
this.adjustInterval(this.maxIntervalMs) // Slow down to 5 min
}
} else {
this.consecutiveGreenSweeps = 0
this.adjustInterval(this.minIntervalMs) // Speed up to 1 min
}
Rationale:
- When healthy: Reduce sweep frequency to save resources
- When issues detected: Increase sweep frequency for faster recovery
Fix Task Generation
LLM Prompt Construction
When issues are detected, the reconciler prompts an LLM:
const messages: LLMMessage[] = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
]
User Message Includes:
- Conflict Markers (if detected):
## Merge Conflict Markers Found
Files with unresolved conflict markers:
- src/auth/login.ts
- src/api/users.ts
- Build Output (if failed):
## Build Output (tsc --noEmit)
```typescript
src/auth/login.ts:15:3 - error TS2322: Type 'string' is not assignable to type 'number'
- Test Output (if failed):
## Test Output (npm test)
FAIL src/auth/login.test.ts
● login() › should return JWT token
expect(received).toBe(expected)
Recent Commits
a1b2c3d4 Implement user authentication (worker-001)
5e6f7g8h Add JWT token generation (worker-002)
5. **Pending Fix Scopes** (deduplication):
Pending Fix Scopes
Fix tasks already target these files — do NOT create duplicates:
src/auth/login.ts, src/api/users.ts
### Task Parsing
```typescript
const rawTasks = parseLLMTaskArray(response.content)
const capped = rawTasks.slice(0, reconcilerConfig.maxFixTasks) // Limit to 5
for (const raw of capped) {
const scope = raw.scope || []
// Skip if all scope files already covered by pending fixes
const allScopesCovered = scope.every(f => this.recentFixScopes.has(f))
if (allScopesCovered) {
continue
}
// Create fix task
const fixTask: Task = {
id: `fix-${fixCounter}`,
description: raw.description,
scope,
acceptance: raw.acceptance || 'tsc --noEmit returns 0 and npm test returns 0',
priority: 1, // Fix tasks get highest priority
...
}
// Track scope to prevent duplicates
for (const f of scope) {
this.recentFixScopes.add(f)
}
}
Staleness Detection
Sweeps are discarded if merges occurred during execution:
const mergeCountBefore = this.mergeQueue.getMergeStats().totalMerged
// ... run build, test, conflict checks ...
const mergeCountAfter = this.mergeQueue.getMergeStats().totalMerged
if (mergeCountAfter > mergeCountBefore) {
logger.info('Merges occurred during sweep — discarding stale results')
return { buildOk, testsOk, hasConflictMarkers, fixTasks: [] }
}
Prevents creating fix tasks based on outdated state.
Callbacks
onSweepComplete()
onSweepComplete(callback: (result: SweepResult) => void): void
Called after each sweep (success or failure).
onError()
onError(callback: (error: Error) => void): void
Called when sweep throws an error.
Usage Example
import { Reconciler, DEFAULT_RECONCILER_CONFIG } from '@longshot/orchestrator'
const reconciler = new Reconciler(
config,
DEFAULT_RECONCILER_CONFIG,
taskQueue,
mergeQueue,
monitor,
reconcilerPrompt
)
// Register callbacks
reconciler.onSweepComplete((result) => {
console.log(`Sweep: build=${result.buildOk} tests=${result.testsOk}`)
if (result.fixTasks.length > 0) {
console.log(`Created ${result.fixTasks.length} fix tasks`)
for (const task of result.fixTasks) {
planner.injectTask(task) // Inject into planning loop
}
}
})
reconciler.onError((error) => {
console.error(`Reconciler error: ${error.message}`)
})
// Start periodic sweeps
reconciler.start()
// Manual sweep (e.g., during finalization)
const result = await reconciler.sweep()
// Stop periodic sweeps
reconciler.stop()
Dependency Injection
interface ReconcilerDeps {
runCommand: ReconcilerRunCommand
completeLLM: ReconcilerCompleteLLM
now: () => number
}
type ReconcilerRunCommand = (
cmd: string,
args: string[],
cwd: string
) => Promise<ExecResult>
type ReconcilerCompleteLLM = (
messages: LLMMessage[],
overrides?: Partial<LLMClientConfig>,
parentSpan?: Span
) => Promise<LLMResponse>
Allows testing with fake command execution and LLM responses.
Best Practices
Fix Task Deduplication:
- Track file scopes in
recentFixScopes set
- Clear set on all-green sweep
- Prevents redundant fix tasks for same files
LLM Timeout Handling:
try {
const response = await this.completeLLM(messages)
rawTasks = parseLLMTaskArray(response.content)
} catch (llmError) {
logger.warn('LLM unreachable — skipping fix task generation (will retry next sweep)')
return { buildOk, testsOk, hasConflictMarkers, fixTasks: [] }
}
Never blocks sweep on LLM unavailability.
Output Truncation:
- Build output: 8000 chars max
- Test output: 8000 chars max
- Prevents LLM context overflow