Skip to main content

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>
)
reconcilerConfig
ReconcilerConfig
required
Configuration for sweep interval and max fix tasks
systemPrompt
string
required
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.
start(): void
Runs sweep() every intervalMs until stopped.

stop()

stop(): void
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:
  1. Type Check: npx tsc --noEmit
  2. Build: npm run build --if-present
  3. Test: npm test
  4. Conflict Scan: git grep -rl "<<<<<<<" -- *.ts *.tsx *.js *.json
  5. Staleness Check: Skip if merges occurred during sweep
  6. 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:
  1. Conflict Markers (if detected):
## Merge Conflict Markers Found
Files with unresolved conflict markers:
- src/auth/login.ts
- src/api/users.ts
  1. 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'
  1. Test Output (if failed):
## Test Output (npm test)
FAIL src/auth/login.test.ts ● login() › should return JWT token expect(received).toBe(expected)

4. **Recent Commits**:

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

Build docs developers (and LLMs) love