# APLflow agent kit, v0.1

Paste this document into a coding agent's context (CLAUDE.md section, Cursor rules,
or system prompt) whenever the agent must read, write, or translate APLflow notation.
Models handle APL poorly from memory and well with these rules in front of them.
Source: https://aplflow.dev

## What you are doing

APLflow notation is standard APL (Dyalog semantics), used as an executable
specification. Your job is one of three tasks:

1. **Read**: explain what a notation expression computes.
2. **Expand**: translate a notation expression into production code (usually
   Python/NumPy) that computes the same function.
3. **Judge**: help build a harness that compares the notation's output (via a real
   APL interpreter) against the expanded code on randomized inputs.

Never "improve" the spec. The notation is ground truth. If it looks wrong, say so;
do not silently change its meaning in the expansion.

## Evaluation rules (these override your instincts)

- Expressions evaluate **right to left**. There is **no operator precedence**:
  `2×3+4` is `2×(3+4)` = 14.
- Functions are **monadic or dyadic by context**. `÷4` (one argument, on the right)
  is reciprocal, 0.25. `12÷4` (two arguments) is division, 3.
- All arithmetic is **array arithmetic**: `1 2 3 × 10` is `10 20 30`. Scalars extend
  to fit. There are no explicit loops in idiomatic notation.
- **Index origin is 1** by default: `⍳5` is `1 2 3 4 5`, not `0 1 2 3 4`.
- `{...}` is an inline function (a "dfn"): `⍺` is the left argument, `⍵` the right,
  `⋄` separates statements.
- A parenthesized **train** of three functions is a fork: `(f g h) x` means
  `(f x) g (h x)`. Example: `avg ← +⌿÷≢` means `avg x = (+⌿x) ÷ (≢x)`.
- `⍝` starts a comment.
- Do **not** confuse APL with J. J is ASCII (`+/ % #`); APL uses glyphs (`+⌿÷≢`).
  If you see ASCII digraphs like `*:` or `%.`, that is J, not this notation.

## Glyph table (the APLflow working subset)

| Glyph | Monadic (one arg) | Dyadic (two args) | NumPy translation |
|-------|-------------------|-------------------|-------------------|
| `←`   | (none)            | assignment        | `=` |
| `⍳n`  | 1..n              | index of          | `np.arange(1, n+1)` (mind origin!) |
| `⍴`   | shape             | reshape           | `x.shape` / `x.reshape(...)` |
| `≢`   | tally (count of major cells) | (none) | `len(x)` / `x.shape[0]` |
| `+ - × ÷` | identity, negate, sign, reciprocal | add, sub, mul, div | elementwise ops |
| `⌈ ⌊` | ceiling, floor    | max, min          | `np.ceil/floor`, `np.maximum/minimum` |
| `*`   | e^x               | power             | `np.exp(x)` / `x ** y` |
| `⍟`   | natural log       | log base          | `np.log(x)` / `np.log(y)/np.log(x)` |
| `\|`  | magnitude         | residue: `a\|b` is b mod a | `np.abs(x)` / `np.mod(b, a)`; note argument order |
| `⌽`   | reverse           | rotate            | `x[::-1]` / `np.roll(x, -k)` |
| `⍉`   | transpose         | dyadic transpose  | `x.T` |
| `f/`  | reduce along last axis | `n f/ v`: windowed reduce | `np.add.reduce(...)`; windowed: `sliding_window_view` |
| `f⌿`  | reduce along FIRST axis | (none)      | `axis=0` |
| `≡`   | depth             | match (exact equality) | `np.array_equal` |

Key axis rule: `⌿` works down columns (`axis=0`), `/` works along the last axis
(`axis=-1`). Getting this backwards is the most common expansion bug.

## Worked examples with expected outputs (self-check before answering)

```apl
⍳5                      ⍝ 1 2 3 4 5
2 3⍴⍳6                  ⍝ matrix: rows (1 2 3) (4 5 6)
+/⍳5                    ⍝ 15
3+/1 2 3 4 5            ⍝ 6 9 12        (windowed sums, width 3)
⌽⍳5                     ⍝ 5 4 3 2 1
10|17                   ⍝ 7             (residue: right mod left)
2*10                    ⍝ 1024
1 2 3×10                ⍝ 10 20 30
2×3+4                   ⍝ 14            (right to left!)

avg ← +⌿÷≢              ⍝ fork: mean over first axis
avg 3 1 4 1 5           ⍝ 2.8

mavg ← {(⍺+/⍵)÷⍺}       ⍝ moving average, width ⍺
3 mavg 100 102 101 105 107
                        ⍝ 101 102.6667 104.3333   (3 values from 5 inputs: no padding)

sm ← {e←*⍵-⌈/⍵ ⋄ e÷+/e} ⍝ numerically stable softmax
sm 1 2 3                ⍝ 0.09003057 0.2447285 0.665241
```

If your reading of an expression disagrees with these outputs, your reading is wrong;
re-derive it using the rules above before proceeding.

## Expansion contract

When expanding notation to Python:

1. Preserve **shape semantics exactly**. `n mavg v` returns `len(v) - n + 1` values:
   no padding, no `mode="same"`, no averaging before a full window exists.
2. Preserve **axis semantics**: `⌿` → `axis=0`; `/` → last axis.
3. Mind **argument order** on asymmetric dyads: `a|b` is `b mod a`; `a⍟b` is log of b
   base a.
4. Write idiomatic, production-quality code (type hints, docstring, vectorized NumPy);
   the notation is the spec, not a style guide.
5. State any place where the notation's behavior is implementation-defined or where
   you were unsure. Do not guess silently.

## Judging contract

A verification harness must treat the notation, run on a real APL interpreter, as the
oracle. Generate randomized inputs covering edge cases (length-1 vectors, window ==
length, negative values, non-square matrices). A disagreement is always a finding:
either the expansion is wrong, or the spec does not say what its author thinks it
says. Both are worth surfacing; neither is the oracle's fault.
