Core
@frites/core (packages/core) is the shared engine, configuration, and type layer for frites. It is the brain that fans a task out to multiple child agents, filters their results through an executable oracle, optionally synthesizes a single best implementation, and reconciles everything into one recommended candidate.
The package is deliberately I/O-free: it has no CLI, MCP, git, or process-spawning code of its own. Everything it touches the outside world with is expressed as a structural interface that @frites/agents and @frites/isolation satisfy at runtime. The only runtime dependency is zod for config validation. This keeps the engine fully unit-testable with fakes.
For the deeper internals (the engine event model, the stage-by-stage flow, and failure modes), see Core engine.
Exports
packages/core/src/index.ts re-exports the public surface:
types.js
AgentSpec, Task, Candidate, OracleResult, OracleCommands, CommandResult, ReconcileDecision, SynthesisInfo, RunResult
events.js
EngineEventHandler, noopEventHandler, and the engine event union
config.js
FritesConfigSchema, FritesConfig, resolveConfig, DEFAULT_CHILD_DIRECTIVE, withChildDirective, pricing schemas
config-io.js
Config loading helpers
pricing.js
estimateCostUsd, pricingFor, UsageTokens
answer-council.js
runAnswerCouncil, decideFanOut, llmJudgeFanOut, parseFanOutVerdict, stripInjectedContext
agent-loop.js
The agentic turn loop
oracle.js
detectOracle, runOracle, runCommand
judge.js
heuristicJudge, diffSize
synthesis.js
Synthesis eligibility, synthesizer selection, prompt construction, reconcile preference
engine.js
runEngine and its structural dependency interfaces
The engine (engine.ts)
runEngine(task, deps, onEvent) is the worktree-mode entry point. It takes a Task, a set of structural EngineDeps, and an event handler, and returns a RunResult.
Structural dependencies
The engine never imports git or a CLI directly. Instead EngineDeps is satisfied by injected implementations:
worktrees: WorktreeManagerLike:resolveBase,create,captureDiff,cleanup, and an optionalapplyDiffToWorktree(satisfied by@frites/isolation).runAgent: RunAgentFn: runs oneAgentSpecin a worktree and returns status, summary, cost, and normalized token usage (satisfied by@frites/agents).runOracle: RunOracleFn: runs build/lint/test against a worktree.oracleCommands: OracleCommands,config: FritesConfig,newRunId: () => string, and an optional external-cancellationsignal.
Flow
Select agents.
selectAgentsusestask.agentsif present, else clonesconfig.defaultAgentsup ton(capped 1-10), suffixing duplicate ids.Resolve base.
worktrees.resolveBasepins the base ref and SHA every worktree branches from.Dispatch + execute (concurrent). Each agent gets its own worktree (created and registered before the prompt runs, so the
finallyalways reaps it), runsrunAgent, and has its diff captured into aCandidate. A candidate's status becomesemptywhen it succeeded but touched no files.Oracle-filter (concurrent). Each succeeded candidate is run through
runOracle. With no executable oracle, candidates carryhadOracle: false.Synthesis (optional). See below.
Reconcile. A pure
reconcile()picks a winner over the original candidate pool, thenapplySynthesisPreferencemay override it with the synthesis candidate.
The whole run is wrapped in a try/finally that Promise.allSettleds worktrees.cleanup over every registered handle, so worktrees are reaped even on a throw.
Reconciliation
reconcile() is pure and emits a ReconcileDecision:
single
Only one agent; it passed the oracle.
tests
The oracle filtered many candidates down to exactly one survivor.
judge
Multiple survivors; tie-broken by heuristicJudge.
synthesis
An oracle-passing synthesized candidate was preferred over the originals.
near-miss
No candidate passed (or none was usable); the closest is surfaced with a warning.
no-oracle
No executable oracle existed; a best-effort pick by smallest diff, explicitly not verified.
The oracle (oracle.ts)
The oracle is frites's objective filter. detectOracle returns explicit build/test/lint commands when given, otherwise (when autoDetect is on) reads package.json scripts and prefixes them with the detected package manager (pnpm, yarn, bun, or npm, chosen from lockfiles). runOracle runs the commands in build → lint → test order, short-circuiting on the first failure, and passes only when at least one discriminating command ran and every one that ran exited 0. runCommand spawns via a shell, keeps a 4000-char output tail, and supports an AbortSignal plus a wall-clock timeoutMs (which the engine wires to config.perChildTimeoutMs).
The judge (judge.ts)
heuristicJudge is the v1 tie-breaker among oracle-passing survivors: it prefers the smallest diff (smallest blast radius) by diffSize (counted added/removed lines, excluding headers), then the fewest files touched. An LLM pairwise judge is a later phase.
Config (config.ts)
FritesConfigSchema is the single zod source of truth for every tunable, and resolveConfig(partial) parses (and defaults) any partial input. It defines child defaults (defaultN, defaultAgents), idle/hard timeouts, budgets, oracle detection, the recursion fuse (maxDepth), fan-out policy and scope, progress/logging knobs, optional per-model pricing, and the full synthesis* family. DEFAULT_CHILD_DIRECTIVE is the thoroughness instruction woven into every substantive child prompt (withChildDirective). The complete key-by-key reference lives in Configuration.
The answer council (answer-council.ts)
The answer council is the transparent-proxy brain for answer/reasoning turns (Stance-A text synthesis): no worktrees or tools; heavy file-editing lives in the engine/MCP path.
decideFanOutis the heuristic gate, honoringconfig.fanOutPolicy(never/always/necessary/auto). Theautoandnecessarypaths inspect prompt length and aHARD_SIGNALkeyword regex (why, compare, design, debug, prove, optimize, …).llmJudgeFanOutupgrades that to a one-word LLM verdict, parsed strictly and fail-closed byparseFanOutVerdict(only a reply beginning withfan-outfans out; anything else resolves to a single agent). It falls back to the heuristic on any error.stripInjectedContextremoves known harness wrapper tags (system-reminder,ide_selection) before classification so the judge sees the real ask.runAnswerCouncilruns N children with diverse framings (drawn fromdefaultAgents), each carrying the child directive and a Markdown-formatting directive, then asks one synthesizer to merge them into a single vetted answer, keeping agreements, adjudicating disagreements, and dropping unsupported claims, without revealing that multiple responses existed.
Synthesis (synthesis.ts)
The synthesis stage integrates the strongest ideas from oracle-passing candidates into one implementation, verified by the same oracle, never a mechanical diff merge.
evaluateSynthesisEligibilityrequires synthesis enabled, an executable oracle, and at leastsynthesisMinCandidatesusable, oracle-passing candidates.selectSynthesizerpicksconfig.synthesisAgent, else the firstclaude-clichild (sosynthesisBudgetUsdactually bites via--max-budget-usd), else the first agent, mapping thesynthesis*budget/timeout overrides onto the returned spec.reservedSynthesisIdallocates a collision-freesynthesis-Nid.buildSynthesisPromptconstructs the strict integration prompt, embedding non-seed candidate diffs smallest-first up tosynthesisMaxDiffCharsand falling back to a file list + read-only worktree path past the cap.applySynthesisPreferenceprefers the synthesized candidate only when it is usable, passed the oracle, and its blast radius is withinsynthesisMaxBlastFactor ×the combined input size; otherwise it falls back to the best original passing candidate and records the reason.
The full design rationale, reconciliation policy, and non-goals live in Synthesis and reconciliation.
Exported types (types.ts)
The type layer is the contract every other package speaks:
AgentSpec: id,kind(claude-cli|codex-cli), optional model, framing, budget, idle/hard timeout overrides, and codexreasoningEffort.Task: instructions,repoPath, optionalbaseRef, acceptance criteria,n, or an explicitagentslist.Candidate: a child's worktree, diff,filesTouched, status (succeeded/empty/errored/timed-out), summary, cost, normalized token usage, and synthesis provenance.OracleResult/CommandResult: per-command output and the overall pass.RunResult:runId,recommended, all candidates/oracle results, thedecision+rationale, acostNote, and (when enabled)synthesis: SynthesisInfo.
These types carry no I/O coupling, which is what lets the engine stay pure.
Last updated