Architecture
naoru harness architecture
The durable reference for the naoru product surface: the orchestration loop, the leaf format, the mission→gate model, and the host contract. (Engine primitives — oracle, fix worker, gates, worktree, executor/outcome seams — are documented at their own modules; this doc covers the product layer composed on top of them.)
The loop (fix_engine.orchestrator)
run_session(leaves, *, executor, safety, mission, repo_root, llm_config=None, llm_config_factory=None, ...) resolves the mission's gate once and drives each enabled leaf through run_leaf:
- run — build an
InvocationSpecfrom the leaf'scommand/cwd/env/timeout_sand launch it via theExecutor(the system-under-test / check command). - judge — evaluate the
RunResultwith the leaf'sPredicate. - green? — if the check already passes, return (
green) with no fix. (Ametricleaf is the exception: a single run only proves the metric is extractable, so it always enters the loop so the comparative perf gate can decide.) - fix loop — up to the per-leaf cap, each attempt isolated in a
LeafWorktree(a git worktree underhost().runtime_dir() / "worktrees"): resolve LLM config only when the first fix attempt needs it, run the fix worker (run_fix_worker) with a generic, tech-stack-agnostic prompt (prompts.build_fix_prompt); re-run the check in the worktree; re-judge. Green and disabled leaves do not resolve LLM config. - gate — on a candidate-GREEN attempt, hand a
GateContextto the mission's gate, which returns aGateDecision(fixed/proposed/rejected).
The language-agnostic correctness signal handed to the oracle is gates_passed = (fix-worker validation_outcome.overall == "ran").
Leaf format (fix_engine.leaves)
A declarative .toml/.json document with a top-level leaves array (or a bare list). Each leaf:
| field | meaning |
|---|---|
id | unique identifier (required) |
command | argv for the check (required; any ecosystem) |
predicate | RED/GREEN judgement (required; see below) |
cwd / env / timeout_s | check invocation context (optional) |
fix_focus | free-form guidance to the fix worker |
description / tags | metadata |
enabled | skip when false (default true) |
max_fix_loops | per-leaf fix-attempt cap (default 2; overridable at run time) |
Predicate kinds (fix_engine.outcome.PredicateKind): exit_code (expected_exit), output_substring (required/forbidden, is_regex), traceback_signature / contract_violation (signature that must be ABSENT once fixed), non_crash, and metric (metric_pattern regex with one float group, metric_direction lower|higher, min_improvement fraction).
Leaf quality is separate from leaf syntax. A high-signal leaf is one narrow, deterministic, machine-checkable command plus a predicate that accurately judges the command output. If a raw project command is not a trustworthy RED/GREEN signal, the target repo should provide a project-local wrapper command that normalizes that command's behavior into a reliable exit code or output contract. Naoru remains tech-stack-agnostic: wrappers own project-specific command interpretation, while leaves declare only argv, context, and predicates.
Mission → gate model (fix_engine.mission)
A Mission selects the commit gate (resolve_gate); GateSpec records its CommitMode:
| Mission | Gate | Commit mode |
|---|---|---|
test-and-fix-bugs | oracle — red-first N-of-M (oracle.run_oracle) + gates_passed | AUTO_COMMIT |
improve-performance | metric — before/after improvement + no other-leaf regression | AUTO_COMMIT |
improve-ux, general-improvement | propose-only — store the worktree diff for review | PROPOSE_ONLY |
The propose-only gate has no commit path, so subjective missions cannot auto-commit regardless of the safety profile (e.g. --unsafe). Commits flow through LeafWorktree.commit_attempt → commit_to_main; a pre-commit-hook rejection or empty diff surfaces as rejected, never a crash. A rejected verdict means the candidate patch was not committed by the gate. It does not necessarily mean the target repo remains red after other accepted fixes; shared root causes can be fixed by a different leaf and verified by a later no-fix run.
Host contract (fix_engine.host)
The engine core imports zero host-product modules; environment-coupled services come from a host installed once via configure_host. HostServices provides invoke (LLM dispatch), resolve_llm_config (inherits the host's configured provider/model — naoru hardcodes none), launch_llm_config (the naoru config picker), discover_tooling, runtime_dir, and is_codex_backed_provider.
Config (fix_engine.config)
Naoru owns a generic role-policy config independent of any host product. The packaged fix_engine/default_config.toml defines analysis, fix, target_orchestrator, and target_implementation, each with tier, optional provider/model/auth pins, reasoning_level, min_reasoning_level, and allow_below_min_reasoning.
load_naoru_config(...) applies: packaged defaults, user config, project .naoru/config.toml, selected profile, session overlay, environment, and CLI overrides. NAORU_CONFIG selects a config file, NAORU_PROFILE selects a profile, and role env vars use NAORU_ROLE_<ROLE>_<FIELD>. naoru config --show-resolved prints the effective JSON without opening the host picker.
Reasoning levels stay provider-neutral (medium, extra high, etc.). The host maps them to provider-specific effort values. If a requested level is below min_reasoning_level and bypass is false, Naoru raises the effective level and records floor_applied.
Discovery (discover_and_configure_host) resolves a provider without naming a host package: an explicit override or NAORU_HOST env (module.path:callable), else a single entry point in the naoru.host group. The default host extra registers obra.naoru_host:install; private host implementations are not part of the public Naoru host contract. A standalone host can supply its own HostServices implementation and config UI later.
Public proposed patches default to host().runtime_dir() / "naoru" / "patches". Host products may pass explicit paths for their own internal harness state.