ARM64 Port Orchestrator — End-to-End Loop Engineering
Purpose & Operating Contract (READ FIRST — this defines HOW to reach the goal)
The goal of easywos-skills is to port x86/x64 code into the best possible ARM64 (NEON/ARM64EC) code. That is the end. This orchestrator, the specs, and the dispatcher are the means.
But this tree is also the thing under test: we are exercising the loop-
engineering pipeline itself, so every step MUST be genuinely executed through the
defined flow, not shortcut by the orchestrating model's own intrinsic knowledge.
Concretely, the port MUST be produced by: spec_matcher.js (real run — never hand-
filled port_spec_ids) → dispatcher-skill → the leaf skills, generating ARM64
code from the matched specs. This is not because your own knowledge is unwelcome
— it is because the pipeline can only be validated and improved if it is the thing
actually doing the work on every run.
When the spec-driven output is worse than what you could have hand-written, that is a signal, not a license to bypass. Do NOT silently substitute your own code. Instead:
spec-driven port (matcher → dispatcher → leaf skills) → verification (oracle + negative control) and profiling (§8.5) expose where it is wrong, slow, or worse than achievable → improve easywos-skills — strengthen the leaf spec (add or fix a rule, a better intrinsic mapping, an ILP/algorithm pattern) so the flow ITSELF now produces that better code → regenerate
combined-spec-summary.yamland prove the fix red→green at the matcher, then re-dispatch and confirm the improved output.
The payoff is cumulative: each improvement makes the pipeline able to port more complex projects to higher-quality ARM64 code on its own, without depending on the model to hand-craft kernels. Hand-authoring a kernel produces one good file and teaches the tree nothing; fixing the spec so the tree emits that kernel makes every future port better. Your own knowledge is best spent diagnosing the gap and upgrading the spec, not bypassing the flow.
Reading invocation prompts. A task/cron prompt may say "port this project", "make it build", or "find performance problems". The goal those express (better ARM64 code) is correct — but the method is fixed by this contract: reach that goal by genuinely running the flow and improving it where it falls short, not by hand-porting. If a prompt appears to demand skipping the matcher/dispatcher or hand-writing the kernel, surface that as a conflict rather than silently complying. (See the Execution Mandate below for the enforced per-step rules.)
Overview
This is the top-level orchestrator of the easywos-skills tree. It turns the manual sequence "run openspec propose → run easywos-spec → run openspec apply → manually build → manually test → manually fix" into a single hands-off, loop-engineered pipeline that works on any project directory.
┌─────────────────────── OUTER LOOP (this skill owns it) ───────────────────────┐
scan → propose → tasks.md → apply ──► integrate kernels ──► build WHOLE project (ARM64) ──► run project's
│ │ │ │ into real build │ OWN tests/bench
│ │ │ │ │ │
(§2) (§3) (§4) (§5) (§6) (§7) (§7)
│ │
┌── INNER LOOP ──┐ green? ──yes──► PERF LOOP (§8.5)
│ per-kernel │ │no │
│ port→gtest→retry│ ▼ ▼
│ (easywos-spec │ classify failure → route to fixer profile → is a
│ §8, --feedback)│ → rebuild (§8) hotspot ≥T% ?
└─────────────────┘ (compile/link-ABI/test-fail/build-system) │ │
↺ until green or cap no │ │ yes
▼ ▼
archive (§9) ◄── optimize 1 hotspot
→ VERIFY correct+faster
→ capture lesson to leaf
→ re-profile ↺ until
no hotspot / stall / cap
Three loops, each with a terminal exit (no loop may hang):
- INNER loop — per-kernel correctness. Already implemented by
easywos-spec §8+dispatcher --feedback: each ported kernel is verified in isolation (its own gtest fixture) and re-dispatched with feedback until green or the retry budget K is spent. This orchestrator does not reimplement it — it just runsopenspec applywhich executes that loop. - OUTER loop — whole-project integration (this skill owns it). After the kernels are individually correct, they must compile, link, and pass inside the real project and against the project's own test/bench suite. Any failure there (a build-system gap, a link/ABI mismatch, a project-level test regression) is classified and routed to the right fixer, then the project is rebuilt — until the integrated project is green or the iteration cap is hit.
- PERF loop — post-port optimization (NEW; this skill owns it, §8.5). A port
can be correct but slow — most often an x64 SIMD path ported to a scalar
fallback. Correctness tests never catch this; only a profiler does. After the
project is green, this loop profiles the ARM64 build with the
profilingskills, and while a hotspot sits above a threshold it optimizes one hotspot, independently verifies both correctness (negative control) and a real speedup, captures the verified technique back into a leaf skill, and re-profiles — until no significant hotspot remains, a stall, or the cap. It is optional and additive, and never weakens a correctness gate to gain speed.
The orchestrator is generic: nothing below is x265-specific. All project facts (path, build system, test command) are discovered at run time (§1, §7.1) or passed as arguments.
Loop-engineering decision model (how authority is delegated)
This skill follows the "push decisions down to the right layer" model. Three kinds of decision, each handled by the right actor — never all by one agent:
- ① Orchestration decision — what to do, in what order, whether to retry or stop. This is deterministic code, never free LLM judgement: the outer loop lives in assets/outer-loop-driver.js (run via the Workflow tool), where the loop, fan-out, retry counters, and convergence/stall criteria are hardcoded. The skeleton is code; agents only fill it in. (§7.2, §8)
- ② Task decision — how to do one specific thing. Delegated to a fixer agent inside a bounded box: one owning file, a clear method, and an objective acceptance test (the rebuild). (§8 step 2)
- ③ Judgement decision — is the work actually correct. Handled by independent verification, never by the agent that did the work: objective gates (compile/link/test) plus an adversarial verifier agent that must refute the fix or watch the test go red under a negative control before it is trusted. (§8 step 3)
Backed by three principles enforced throughout: narrow boundaries (each agent
decides in a small space), decision ≠ verification (the maker never grades
itself — a green isn't real until seen red on a counterexample), and state
externalized (all cross-round decisions land in files — the state file, tasks.md
checkboxes, feedback/*.yaml, and OUTER-PROGRESS.md — so the loop is
resumable, reproducible, and debuggable).
Execution Mandate: the port MUST be produced by the leaf skills, not by you (READ FIRST — NON-NEGOTIABLE)
The goal is the best possible ARM64 code — but it must be reached by RUNNING the pipeline, so the pipeline itself gets better (see the Purpose & Operating Contract above). The value chain this flow exists to run is:
port using spec knowledge (via
dispatcher-skill→ leaf skills) → the spec has a gap → the spec-driven code is wrong, slow, or worse than achievable → verification + profiling (§8.5) find it → improve easywos-skills so the flow itself now emits the better code (then prove it red→green at the matcher).
Every link depends on the first one. If you (the orchestrating model) hand-write the NEON port from your own intrinsic knowledge instead of driving the leaf skills, the chain is broken at the root: the pipeline was never exercised, so any gap you later "find" is an artifact of your code, not of the flow, and there is nothing real to fix back into the tree (and it can be circular — you write the naive version a spec would have avoided, then "discover" it). This actually happened and wasted a run; do not repeat it. Hand-authoring yields one good file and teaches the tree nothing; improving the spec makes every future port better.
Therefore, MANDATORY on every run — no shortcuts, even when you are confident you could write the kernel yourself:
- Run the real matcher.
port_spec_idsin the matched YAML MUST come fromeasywos-spec/scripts/spec_matcher.js, never hand-filled. Hand-guessed IDs are routinely wrong (observed: guessed[106], matcher actually returns 97/146/151/152). - Dispatch to the leaf skills. Each
tasks.mditem's port MUST be produced by actually invoking/dispatcher-skill <id> --specs <ids> --source <matched-yaml>(which routes to thesse-avx-to-neon/intrinsics-x64-to-arm64/asm-*leaf skills), so the code reflects what the spec knowledge yields — including its blind spots. Spawn a subagent as the leaf skill if needed, but the port is the leaf skill's output, following the matched specs, not your from-memory version. - You orchestrate and verify; you do not author. Your role is: run the matcher, dispatch, then apply the objective gates (scalar oracle + negative control + §8.5 profile). Decision ≠ verification — the thing that makes the code must not be the thing that grades it, and here the maker must be the leaf skill.
- A clean spec-driven port is a valid, valuable outcome; a worse-than-achievable one is a spec-improvement task, not a bypass. If the leaf skills produce good code, record CONVERGED — positive confirmation the spec is good. Do NOT manufacture a slow version to "find a problem"; a real problem only counts if the spec-driven output exhibits it. If the spec-driven output IS worse than what you know is achievable, the correct response is to upgrade the leaf spec so the flow emits the better code (then §5-re-dispatch and confirm), never to quietly swap in your hand-written kernel.
- Closing the loop requires red→green at the matcher. A spec fix is not
promoted until
combine-specs.jsregeneratescombined-spec-summary.yamlAND the matcher then surfaces the new/edited spec on the report (it did not before). A leaf-yaml edit without regeneration is a dormant fix.
If for any reason a port is produced by hand rather than by the leaf skills, that is a skipped step — say so plainly in the report (faithfulness over completion); never present a hand-written port as a spec-driven result.
Verification & Measurement Discipline (READ FIRST — applies to EVERY port)
These are cross-cutting process rules — not tied to any one intrinsic — that govern every §5 correctness check and every §8.5 measurement. They were learned across many real ARM64 porting runs; skipping one produces a false green or a false speedup. Apply them on every run; the full rationale + the failure each was learned from is in references/verification-measurement-discipline.md.
- Negative control must EXCEED the tolerance. A perturbation smaller than the pass tolerance still passes — the control is silently useless. Flip a mantissa bit / scale an input ±50%, not ±1 ULP. Prove red, revert, prove green.
- Oracle must match the ported SEMANTICS/LAYOUT. Verify against the exact
arithmetic of the kernel you ported (not the library's textbook scalar), and
against its own SIMD layout (round-trip
unpack(pack(x))==x, not a naïve linear layout). Mind rounding (vcvtnqround-to-nearest ≠ truncatingvcvtq≠ scalarlrintf). - Near-zero references need ABS-OR-RELATIVE tolerance. Pass if
abs_err<abs_tolORrel_err<rel_tol; a pure-relative metric flags correct near-zero results. - Defeat benchmarking traps. Loop-hoisting/dead-store elimination and
first-touch page faults make you measure nothing or the OS. Make every result
live (serial-dependent inputs + consume all outputs +
volatileglobal sink), use large iteration counts, subtract a warmup pass (~6s+ steady state). - Decide A-vs-B by PAIRED back-to-back measurement. Do not compare CPU-ms from two ETLs collected minutes apart (load drifts). Run A,B,A,B… alternating and compare paired wall-clock; use the profiler for attribution (genuine kernel work vs fallback), not cross-collection magnitude.
- Faithfulness over completion. A green never seen red is unverified; a speedup not re-measured is unverified; a synthetic-workload win stays a candidate until a second real-workload datapoint corroborates it.
- Profile the ported kernel against the REAL fallback — "correct but slower" is
a defect class no correctness check can catch. A port can pass every KAT,
oracle and asm-equivalence test and still be slower than the portable C it
replaced (measured case: a ChaCha20 port that narrowed 4-block-parallel asm to
one block per iteration was 1.22x SLOWER than wolfSSL's C; widening it back to
4-wide made it 1.96x FASTER, same tests). Therefore:
- Compare against the actual fallback the OFF build runs, not a baseline
you wrote yourself — a hand-written baseline flatters the port. If the real
fallback is
static, EXTRACT it verbatim and validate the extraction against a KAT before trusting it (a silently-empty macro in an extraction once made the "baseline" run 6x too fast). - Gate every benchmark on a checksum both sides must match; unequal work makes the timing meaningless, and this is what catches a broken baseline.
- Treat slower than portable C as a signal to widen the parallelism, not to revert the port.
- Compare against the actual fallback the OFF build runs, not a baseline
you wrote yourself — a hand-written baseline flatters the port. If the real
fallback is
0. Invocation & Arguments
Harness compatibility. The /arm64-port-orchestrator … form below is the
Claude Code slash-command shorthand. This skill is harness-agnostic: on Codex
(and any harness without slash-command skills) there is no /command — invoke it
by describing the task in natural language and passing the same arguments, e.g.
"Use the arm64-port-orchestrator skill to port <project-path> to ARM64, build
cmd <...>, autonomy hands-off." The options table applies identically; only
the surface syntax differs. Do not assume a slash-command runtime exists.
/arm64-port-orchestrator <project-path> [options]
| Argument | Required | Default | Meaning |
|---|---|---|---|
<project-path> |
Yes | — | Root of the project to port (absolute or repo-relative). |
--change-name <slug> |
No | <project>-arm64-porting |
OpenSpec change id to create. |
--build-cmd "<cmd>" |
No | auto-detected (§7.1) | Command that builds the WHOLE project for ARM64. |
--test-cmd "<cmd>" |
No | auto-detected (§7.1) | Command that runs the project's own test/bench suite. |
--target arm64-windows | arm64-linux |
No | arm64-windows |
Toolchain family for build/test. |
--max-outer-iters N |
No | 12 |
Cap on the outer build→fix loop (§8). |
--inner-retry-K N |
No | 3 |
Per-kernel retry budget handed to easywos-spec §8 / dispatcher. |
--profile / --no-profile |
No | --profile |
Run (or skip) the post-port performance loop (§8.5). Only --no-profile or no runnable terminating workload may skip profiling; a non-elevated host pauses the run before profiling and does not archive. |
--run-workload "<args>" |
No | — | A terminating workload for the profiled target (§8.5). If absent, §8.5 tries the project's own bench target, else STALLs with that reason. |
--perf-hot-pct N |
No | 10 |
Stop threshold for §8.5: a hot leaf below this %CPU self-time is not worth optimizing (the CONVERGED signal). |
--perf-max-iters N |
No | 6 |
Cap on the §8.5 profile→optimize→verify loop. |
--capture-lessons / --no-capture-lessons |
No | --capture-lessons |
Whether §8.5 feeds each verified optimization back into a leaf skill spec (real ports only). |
--synthetic-workload |
No | off | Mark the profiled target as a synthetic/test program. §8.5 still profiles and optimizes, but lessons are written to PERF-PROGRESS.md only — never merged into the shared skill tree (a test program is not general evidence). |
--autonomy hands-off | checkpoint |
No | checkpoint |
hands-off runs all phases without pausing; checkpoint pauses for confirmation at the phase gates in §1.3. |
--resume-from <phase> |
No | — | Skip to a phase (propose/apply/integrate/build/perf/archive) when re-running after an interruption; earlier phases are assumed complete and verified. |
State file. The orchestrator maintains openspec/changes/<change-name>/.orchestrator-state.yaml
recording the phase reached, the matched-YAML path, the build/test commands, and
the outer-loop history. It is the resume anchor for --resume-from and the
audit trail; update it at every phase boundary.
0.1 Harness compatibility — subagents, OpenSpec, and parallelism (READ before running on Codex)
This skill's prose uses several Claude Code primitives. They all have a Codex (or generic-harness) equivalent — the pipeline is harness-agnostic, only the mechanism differs. Translate as follows and never let a missing Claude primitive stop the run:
| Claude Code term used below | What it means | Codex / generic equivalent |
|---|---|---|
| "Spawn a subagent" (as a leaf skill / fixer / verifier) | delegate a bounded task to a fresh agent context | Codex has no Task-tool subagent. Either do the step in-context by loading the relevant leaf skill's SKILL.md and following it, or use Codex's own delegation/agent mechanism if configured. The requirement is only that the port/fix is produced by following the leaf skill's spec-driven guidance, not that a separate agent process exists. |
Workflow({scriptPath,args}) |
run a deterministic driver scriptlet in the Workflow runtime (it relies on injected args/agent/parallel/log/phase + top-level await) |
Codex has no Workflow runtime, so the driver files are not runnable with plain node. Instead enact the same deterministic algorithm they encode — the spec is in references/outer-loop.md / references/perf-optimize-loop.md — using Codex's own tools. The control flow (code owns "loop again vs stop") is what must be preserved (see §7.2). |
OpenSpec opsx:propose / opsx:apply / opsx:archive (skills openspec-propose / openspec-apply-change / openspec-archive-change) |
Claude Code slash/skill wrappers around OpenSpec | Use the OpenSpec CLI directly: openspec propose, openspec apply, openspec archive (or your harness's OpenSpec integration). OpenSpec is optional — see the demo-without-openspec path in easywos-spec. |
| "fan out … one fixer agent per file, in parallel" | independent files fixed concurrently | Parallelism is an optimization, not a requirement. On a harness without parallel subagents, process the independent fixes sequentially — the deterministic driver's per-file grouping and retry counting are unchanged; only wall-clock differs. Never let two edits touch the same file concurrently regardless. |
Read / Write / Edit / Grep tool names |
file/search operations | Use whatever equivalent file-read/write/search tools the current harness provides; the operations are generic. |
The invariant across harnesses: the port is produced by the spec-driven flow (matcher → dispatcher → leaf skills), the loop is driven by the deterministic scripts, and every fix is independently verified. How you spawn work or invoke OpenSpec is harness-specific; that discipline is not.
1. Preflight, Autonomy & Phase Gates
1.1 Dependency preflight (MUST run first)
Before anything else, confirm every skill this orchestrator composes is present.
Reuse the same install-then-stop policy as easywos-spec §0.
| Dependency | Path | Role |
|---|---|---|
easywos-spec |
skills/easywos-spec/SKILL.md |
Spec match + tasks.md + inner verify/retry loop (§4, §5) |
dispatcher-skill |
skills/dispatcher-skill/SKILL.md |
Per-item routing / re-entry point (§5) |
arm64-porting-report |
skills/arm64-porting-report/SKILL.md |
Scan → EasyWoS porting-report YAML (§2) |
enable-windows-arm64 |
skills/enable-windows-arm64/SKILL.md |
Generic build-system detect/enable (§6, §7.1) |
arm64-baseline-porting |
skills/arm64-baseline-porting/SKILL.md |
Freeform / fallback constraints |
| leaf skills | skills/{asm-x64-to-arm64,sse-avx-to-neon,intrinsics-x64-to-arm64,arm64-inlineasm-to-intrinsics}/ |
Migration execution |
| profiling skills | skills/{etl-generator,perf-sampling-parser,perf-optimizer}/ |
Post-port CPU profiling + root-cause (§8.5) |
leaf-skill-creator |
skills/leaf-skill-creator/SKILL.md |
Capture a verified optimization back into a leaf spec (§8.5) |
| combined specs | skills/combined-spec-summary.yaml |
Global spec table |
| OpenSpec | openspec/ present in <project-path> (or its repo) with config.yaml |
Change lifecycle |
If any are missing, follow easywos-spec §0.3: regenerate
skills/combined-spec-summary.yaml when that is the only missing generated
file; otherwise report the missing dependencies and ask before running
npx skills add qualcomm/EasyWoS/agent --all. Do NOT proceed on a partial
toolchain.
1.2 Toolchain preflight
Confirm the build/test host can actually build ARM64 for the chosen --target:
arm64-windows: a native Windows-on-ARM64 host (no emulation) with MSVC ARM64 tools + ClangCL,cmakeon PATH;armasm64if any output is armasm-dialect.asm. (Seeeasywos-spec §7.5/§7.6.)arm64-linux: an aarch64 host or a working cross toolchain.
Prove the toolchain works with a trivial invocation (cmake --version, compiler --version) before trusting later "build failed" signals — a missing compiler must be reported as a toolchain gap, not auto-"fixed" as a code bug.
1.3 Autonomy & phase gates
In --autonomy checkpoint (default), pause and summarize for user confirmation
at these gates: after §2 (scan report), after §4 (tasks.md generated),
before §8.5 (perf loop — since it captures lessons into the shared skill tree),
and before §9 (archive). In --autonomy hands-off, do not pause — but still
honor every mandatory STOP (missing deps §1.1, toolchain gap §1.2, ambiguous
build/test command §7.1, outer-loop non-convergence §8.4, perf STALL §8.5.2).
Autonomy tunes optional pauses; it never disables a safety STOP.
2. Scan → EasyWoS Porting Report
Produce the EasyWoS input the rest of the pipeline consumes.
- Invoke the
arm64-porting-reportskill against<project-path>to scan for x64-specific code (hand-written asm, SSE/AVX intrinsics, arch guards) and emit an EasyWoS YAML withschema: x64-to-arm64-portingand aporting_itemsarray. Save as<project-path>/<project>-arm64-porting-report.yaml. - Validate the output: non-empty
porting_items, each withfile_path+code_rangeresolvable from the report's directory (mirrorseasywos-spec §1.2). - Checkpoint gate (§1.3): report the item count and a one-line summary per item. If zero items, STOP — either the project has no x64-specific code to port, or the scan config is wrong; do not fabricate work.
If the user already has a hand-authored porting-report YAML, accept it via a
--report <path>override and skip the scan.
3. OpenSpec Propose
Create the change that will carry the port.
- Run the OpenSpec propose flow (Claude Code: skill
openspec-propose/opsx:propose; Codex/generic:openspec proposeCLI — see §0.1) to createopenspec/changes/<change-name>/withproposal.md,design.md, and atasks.mdplaceholder. The proposal's scope is "port to ARM64: migrate the N scanned porting_items, integrate into the real build, and pass the project's own test suite on ARM64." - Ensure
openspec/config.yamlhas therules.tasksentry that mandateseasywos-specfor porting_item changes (as in this repo'sconfig.yaml). If absent, add it — that rule is what makes §4 fire automatically on task generation. - Record the change path in the state file.
4. Task Generation via easywos-spec (drives the INNER loop's plan)
Delegate tasks.md generation entirely to easywos-spec — do not hand-write
tasks, and do not hand-fill port_spec_ids: they MUST be produced by actually
running easywos-spec/scripts/spec_matcher.js against the report (hand-guessed IDs
are routinely wrong — see the Execution Mandate). Pass it the porting-report YAML
from §2. It will:
- run script screening + LLM refinement, emit
<report>-matched.yamlwithport_spec_ids+match_confidence(easywos-spec §2–4), - generate
tasks.mdwith one/dispatcher-skillinvocation per item plus a Verification section that already encodes the inner verify→retry loop (easywos-spec §5, §7, §8), - decompose any whole-file assembly items into per-kernel group children (easywos-spec §1.4).
Checkpoint gate (§1.3): surface the generated tasks.md — item count, any
[NEEDS REVIEW] low-confidence items, and the chosen verification flow
(assembly / intrinsics / both). This is the plan the inner loop executes.
5. OpenSpec Apply — Port + Inner Verify/Retry Loop
Run the OpenSpec apply flow (Claude Code: skill openspec-apply-change /
opsx:apply; Codex/generic: openspec apply CLI — see §0.1) to
work through tasks.md. This executes, per item:
/dispatcher-skill <id> --specs … --source <matched-yaml>→ leaf skill emits ARM64 output,- the inner loop (easywos-spec §8): build the item's gtest fixture, run it,
and on failure write
feedback/<id>.attemptN.yamland re-dispatch with--feedbackuntil the fixture is green and was shown red at the V.0 negative-control gate, or the retry budget--inner-retry-Kis spent (→[NEEDS REVIEW], batch continues).
HARD GATE (see the Execution Mandate above): the NEON port MUST be the leaf skill's output, never hand-written by the orchestrating model. The dispatcher loads the matched specs and the leaf skill emits the ARM64 code from that spec knowledge — that is the only way §8.5 can later reveal a genuine spec gap rather than an artifact of your own coding. If you skip the dispatcher and write the kernel yourself, the entire "spec-driven → profile → fix-the-spec" loop is invalidated. Spawn a subagent to act as the leaf skill if the dispatcher cannot run directly (on Codex, which has no subagent, do this step in-context by loading the leaf skill's SKILL.md and following it — see §0.1), feed it ONLY the matched specs (not your own solution), and treat its output as the port. Never hand-fill
port_spec_ids— they come from the matcher (§4). If a hand port is unavoidable, mark the item and report it as a skipped/unverified spec-driven step; do not pass it off as spec-driven.
The orchestrator's job here is to launch and monitor, not to re-implement:
- Pass
--inner-retry-Kthrough as easywos-spec's K. - When apply finishes, read tasks.md checkboxes: collect items marked
[x](verified),[NEEDS REVIEW](retry-exhausted), and any unchecked (skipped). - Do not enter the outer loop until every item is either
[x]or explicitly[NEEDS REVIEW]. A[NEEDS REVIEW]kernel is allowed to proceed (it may not be exercised by the project tests, or its defect may surface at §7 and get fixed there), but it MUST be reported, not silently treated as done.
Trust rule (inherited from global working principles + easywos-spec §7.6): never count a fixture as passing unless it was observed failing under a deliberate perturbation. If apply reports greens that were never shown red, treat them as unverified and force the negative control before trusting.
6. Integrate Kernels Into the Project's REAL Build
This is the bridge between "kernels correct in isolation" (§5) and "whole project builds" (§7) — the step most manual pipelines forget. It is generic: discover how the project already wires its existing asm/arch-specific sources, then wire the ARM64 outputs the same way.
- Detect the build system with
enable-windows-arm64's detector (scripts/detect_build_system.py) and its ARM64-enable step so the project has an ARM64 build configuration/preset at all. - Find the existing arch-specific integration point. Locate how the project
compiles its current SIMD/asm sources (e.g. an x86 source list, an assembler
custom command, a
setupAssemblyPrimitives-style dispatch table,#ifdefarch guards). This is the template. - Register the ARM64 outputs by mirroring it:
- add the ported
.S/.asm/.cfiles to the build under the ARM64 guard, mirroring how the existing arch sources are listed; - create/extend the dispatch table so the ARM64 kernels are actually called (a registration TU mirroring the existing one, guarded by the project's ARM64 macro), and matching headers with the exact C-ABI prototypes;
- only register a symbol that actually exists in the emitted output — inspect the files; never invent symbols.
- add the ported
- Idempotent. If integration already exists (re-run /
--resume-from), detect it and change nothing. Record what was wired in the state file.
Ground every edit in the surrounding code's idiom and the project's own arch macros. When the correct wiring is ambiguous, prefer registering fewer, certain kernels — the outer loop (§7/§8) will surface anything missing as a link error and route it back here.
7. Build the WHOLE Project & Run Its OWN Tests (outer-loop body)
7.1 Resolve build & test commands (once)
If --build-cmd/--test-cmd were not given, auto-detect and CONFIRM before use
(a wrong test command silently reports false green — a mandatory STOP if
ambiguous):
- Build: from the detected build system + target. E.g. CMake/Windows-ARM64:
cmake -S <src> -B <build> -A ARM64 -T ClangCL <arch/test flags> && cmake --build <build> --config Release. Reuse the project's own ARM64 preset/toolchain file if present (e.g. aarm64-windows-clangcltoolchain, aCMakePresets.jsonARM64 preset). - Test/bench: discover the project's OWN suite — do not settle for the
isolated verify-gtest from §5. Look for, in priority order: a CTest/
ctestregistration, a test/benchmark target or binary (e.g. a*TestBench,*_test,checktarget,checkasm), amake check/ninja test, or a documented test command in the project README/CI. Enabling tests may require a build flag (e.g.-DENABLE_TESTS=ON); fold it into the build command. - Record both commands in the state file. See references/build-test-detection.md.
7.2 The outer loop is driven by a DETERMINISTIC script, not by prose
The loop control — iterate, fan out, count retries, decide "loop again vs. stop", detect no-progress, exit — is hardcoded in assets/outer-loop-driver.js, run via the Workflow tool. This is the orchestration-layer (①) decision, and per the loop-engineering principle it is CODE, not an LLM judgement: a single bad agent turn cannot make the loop run away, skip verification, or archive early, because those transitions are not the agent's to make. Agents only FILL IN two delegated decisions:
- ② task-layer — "how do I fix this one file" (the fixer agents), and
- ③ judgement-layer — "is this fix actually correct" (the verifier agents).
Invoke the driver once build/test commands are resolved (§7.1), passing all
project facts as args (nothing is hardcoded in the script):
Workflow({ scriptPath: "<skills>/arm64-port-orchestrator/assets/outer-loop-driver.js",
args: { projectPath, buildCmd, testCmd, matchedYaml,
stateDir: "openspec/changes/<change>/",
maxOuterIters: <--max-outer-iters>, innerRetryK: <--inner-retry-K> } })
Do not re-implement the loop by hand-running build/fix steps in the main
conversation — that reintroduces exactly the "LLM decides whether to continue"
failure mode the driver exists to remove. The driver returns
{ status: CONVERGED | STALL, reason, iterations, finalTests, needsReview, history, progressLog }.
Harness compatibility (driver invocation).
Workflow({ scriptPath, args })above is the Claude Code tool form. The driver files are Workflow scriptlets, not standalone Node CLIs — they rely on runtime primitives the Workflow tool injects (args,agent(...),parallel(...),log,phase, top-level await), sonode …/outer-loop-driver.jswill NOT run them. On Codex (or any harness without the Workflow runtime), do not try to execute the driver file; instead enact the same deterministic algorithm it encodes — build/test → classify → per-file fix → independent verify → retry-budget → CONVERGED/STALL — using Codex's own tools, following the harness-neutral spec in references/outer-loop.md (and references/perf-optimize-loop.md for §8.5). What must not change across harnesses is the control flow (code/algorithm owns "loop again vs stop"), not the mechanism used to run it.
8. Auto-Fix Loop Internals (what the driver does each iteration)
The driver embodies the three-layer decision model. Each iteration:
Build stage — objective gate (not an agent opinion). One agent runs
buildCmdthentestCmd, and returns a structured classification of every distinct root failure (category + owningtargetFile+kernelId). Red/green is decided by the tools, not by an agent's say-so. Success = configure+build+ link clean AND the project's own suite ran with zero failures.Fix stage — ② task-layer, one fixer per owning file (bounded box). Failures are grouped by the file that owns them; one fixer agent per file, independent files fixed in parallel, never two agents writing the same file. Each fixer gets a tight box: only this file, minimal root-cause fix, with method by category:
Failure category Owning fixer Box / method build-systemenable-windows-arm64+ §6 integrationfix build files / ARM64 flag / source registration compile-asm/compile-cppleaf skill discipline via targeted edit fix the owning .S/.cpp/headerlink-abi§6 integration + leaf skill fix the symbol's owner (export name / prototype / registration) test-failurere-enter the INNER loop for that kernelIdwrite feedback/<id>.attemptN.yaml(dispatcher §2.7.1) and re-dispatch with--feedback; the project's own test is now the oracleVerify stage — ③ judgement-layer, DECISION ≠ VERIFICATION. The agent that made a fix does not bless it. A separate, independent verifier agent adversarially checks each applied fix (default verdict: not verified):
- non-test fixes → try to refute (find a counterexample; re-run the build/ link step to confirm the specific error is gone and nothing new broke);
test-failurefixes → mandatory negative control: perturb the test's reference side, watch it go[FAILED], revert, watch it go green. A fix that can't be shown red is unverified — the test isn't wired to the kernel. Only independently verified fixes let the loop proceed to rebuild. Refuted fixes are recorded so the next round doesn't retry the dead approach.
Retry budget (owned by the code). Per-kernel test-failure retries are
counted by the driver against innerRetryK; on exhaustion the kernel is marked
[NEEDS REVIEW] and no longer re-fixed — one hard kernel never stalls the batch.
State externalization. Each iteration appends to
openspec/changes/<change>/OUTER-PROGRESS.md: the failures seen, fixes applied,
which were verified vs. refuted (with counterexample), so a resumed or next
iteration never repeats a dead end. Per-kernel feedback/*.yaml carries the
inner-loop signal (as in §5).
8.4 Terminal exit (the driver ALWAYS returns one)
CONVERGED— whole project builds + the project's own suite green + every trusted test fix was shown red under negative control → §9 (archive).STALL(reason)— iteration cap hit, zero fixes applied, failure signature unchanged acrossstaleStoprounds, all fixes refuted, or only retry-exhausted kernels remain. Do not archive; report the surviving failures (category + owning file + message) and the single most likely next action; leave the change applied-but-not-archived for a human.
A run that neither converges nor stalls is a bug in the driver, not a valid state — every code path returns one of the two.
8.5 Post-Port Performance Loop (profile → optimize → verify → capture)
Runs after §8 reaches CONVERGED (the whole project builds and passes its own
tests on ARM64) and before §9 archive. Skipped only when --no-profile is
given or when no runnable terminating workload can be resolved. If profiling is
enabled and the current agent harness (Codex, Claude Code, etc.) or terminal is
not elevated, stop at this phase: record the resume point and tell the user to
restart the harness from an Administrator terminal, then resume from --resume-from perf with the same project/workload arguments. Do not run profiling,
optimization, lesson capture, or archive from the non-elevated session.
Why it exists. A port can pass every correctness gate and still be slow —
the classic case is an x64 SIMD kernel that was ported to a scalar fallback
(per-element fminf/sqrtf/expf, a NEON block gated out by #ifdef __GNUC__,
a portable:: path with no ARM64 specialization). The inner (§5) and outer (§8)
loops are correctness loops; they are green on slow-but-correct code. Only a
profiler catches it. This phase adds that missing signal and, uniquely, feeds
what it learns back into the skill tree.
8.5.1 The loop is a DETERMINISTIC driver (same model as §7.2/§8)
Loop contr
…(truncated)