APLflow

The loop, worked end to end

One small, real example: a moving average. Human writes the spec in notation, agent writes the production code, interpreter judges the result. Including the part where the agent gets it wrong and the oracle notices.

Status: the numbers on this page are real (the Python outputs were executed and checked, and the APL follows standard Dyalog semantics), but this page walks the loop by hand. The runnable notebook that automates it is the next milestone; see where this goes.

Step 1 · The spec, written by a human

A moving average of width . In APL, ⍺ +/ ⍵ is a windowed sum: slide a window of width along the vector , summing each window. Divide by the width and you have the moving average. That is the entire specification:

mavg ← {(⍺+/⍵)÷⍺}

3 +/ 1 2 3 4 5          ⍝ windowed sums, width 3
6 9 12

3 mavg 100 102 101 105 107
101 102.6667 104.3333

Note what you did not have to specify in English: that the output is shorter than the input (a width-3 window over 5 points yields 3 windows), that there is no padding, that nothing is averaged before enough data exists. The notation's semantics settle all of it, unambiguously. An English sentence like "compute a 3-day moving average of the prices" settles none of it.

Step 2 · The expansion, written by an agent

Hand the spec to a coding agent and ask for production Python. A correct expansion:

import numpy as np

def mavg(x: np.ndarray, n: int) -> np.ndarray:
    """Moving average of width n. Returns len(x) - n + 1 values."""
    windows = np.lib.stride_tricks.sliding_window_view(x, n)
    return windows.mean(axis=-1)

More lines, more names, more machinery: exactly the boilerplate you no longer want to write by hand, in the language your production stack, your colleagues, and your CI already speak.

Step 3 · The judge

Now the notation stops being documentation and starts earning its keep. It executes, so it is the test oracle, and it is independent: you wrote it, on an interpreter with forty years of settled semantics, while the Python was written by a machine moments ago.

for _ in range(1000):
    x = np.random.randn(np.random.randint(3, 100))
    n = np.random.randint(1, len(x) + 1)
    assert np.allclose(apl(f"{n} mavg {vec(x)}"), mavg(x, n))
1000 trials · 0 disagreements

What a caught bug looks like

Here is a first attempt an agent can plausibly produce from the English sentence instead of the notation. It reaches for np.convolve and picks the wrong mode:

def mavg(x, n):                          # looks fine in review
    return np.convolve(x, np.ones(n)/n, mode="same")

mavg([100, 102, 101, 105, 107], 3)
[ 67.33  101.    102.67  104.33   70.67]

Wrong length and garbage at both edges, where the window ran off the data and silently averaged against zeros. This is not a contrived strawman; padding and edge handling is exactly the class of decision an ambiguous English spec leaves to chance, and exactly what a same-prompt "vibe verification" test can wave through, because the test-writing agent makes the same assumption. Against the notation there is no argument to have: 3 mavg 100 102 101 105 107 returns three numbers, the Python returned five, trial one fails, the bug never ships.

Why this scales past toy examples

The moving average is deliberately small. The pattern is not: anything you can state as an array transformation (portfolio returns and risk, data cleaning rules, a softmax, an attention block, a windowed anomaly detector) can be one or two lines of notation and therefore an oracle. The agent handles the 10x expansion into engineering; the interpreter handles the judging; you handle the one line that says what correct means. That division of labor is the whole idea.

Next: the working subset of the notation, and the kit that makes coding agents fluent in it.