§1 · 𝕎 is a Descriptor, Not a Self-Evolving Object
A value of type 𝕎 is a static description of a pattern of change. It has four fields:
| Field | Name | Meaning |
|---|---|---|
| f | Frequency | How often the described change repeats |
| A | Amplitude | Magnitude of the change |
| phi | Phase | Offset within the cycle (radians) |
| sigma | Shape | A unit-periodic function σ : ℝ → [−1, 1] |
𝕎 does not mutate or advance itself over time. What changes over time is the scalar obtained by observing it: w.realise(t) — which evaluates to A · σ(f·t + φ). The descriptor stays the same; the observation changes.
This means:
- The descriptor may remain the same across process samples
- The observed scalar can still differ from one τ to the next
- The debugger visualises
realise(tau), not the descriptor
※ w is a static descriptor
let concert_a : 𝕎 = w(f=440, A=1.0, phi=0.0, sigma=sine)
※ realise(t) returns the instantaneous observation
nc> concert_a.realise(0.0)
0.0
nc> concert_a.realise(0.25)
0.9999952938095761
nc> concert_a ※ the descriptor itself is unchanged
⟨440, 1.0, 0.0, sine⟩
§2 · A Process Owns τ
A Process is the runtime object that unfolds over time. On every call to p.sample() the runtime:
- Evaluates the process step at the current τ
- Returns that value
- Advances τ by
1 / rate
τ is local to the process. There is no global clock. Two processes advance their own τ independently. When you need coordination, you must declare it explicitly through entanglement or synchronisation.
§3 · every(w) Emits the Same Descriptor at Each Tick
every(w) does not mutate w. It creates a process that emits the same waveform descriptor each time it is sampled. The process advances; the descriptor does not. The observed scalar is obtained separately through realise(tau).
nc> p = every(concert_a)
nc> unfold(p, 3)
[⟨440, 1, 0, sine⟩,
⟨440, 1, 0, sine⟩,
⟨440, 1, 0, sine⟩] ※ same descriptor, different τ at each step
The three descriptors are identical — what differs is the internal τ at which each was sampled. To observe the change, call realise(tau) on the emitted value.
§4 · Process Combinators Sample Child Processes
Combinators advance child processes through sample(), not by calling step(...) directly. Calling step(...) directly bypasses the child process clock and breaks the semantics.
series(a, b, ...)
- Samples child processes left-to-right
- Feeds each emitted value to the next child as
incoming - If the next child does not accept an incoming argument, it ignores it
- The final child's output is the output of the series
parallel(a, b)
- Samples both children on each tick
- Returns a pair of emitted values
- Children advance independently — no synchronisation unless explicitly declared with
‖
feedback(a, delay)
- Feeds
a's output back into its own input afterdelayseconds - The feedback loop is implemented with a ring buffer of length
ceil(delay * rate) - The initial buffer contents are the zero-amplitude waveform
§5 · Compiler Modes
The compiler has exactly three modes. The mode is the only thing that decides what happens to an intent hole (≔ ??). Explicit bodies compile in any mode without an API call.
| Mode | How selected | Hole behaviour | Network |
|---|---|---|---|
| offline | --offline flag, or no API key present | Tries offline pattern matcher. On miss: raises OfflineHoleError with instructions. | No |
| online | --online flag, with ANTHROPIC_API_KEY set | Calls Anthropic API. Runs AST safety pass. Writes body to snapshot store. | Yes |
| snapshot | --snapshot=<path> flag | Looks up intent_hash in the store. Hit: replays body. Miss: falls through to configured fallback. | No |
The compiler does not silently fall back between modes. If offline mode cannot resolve a hole, it tells you exactly what to do: add the intent to the offline pattern table, run --online, or write the body explicitly. No silent failure.
§6 · The Cache Key
The intent hash is a 16-hex-character SHA-256 prefix computed over the declaration's shape: name, kind (fn / process / let), argument types, return type, and the text of all three intent clauses (intent, forbid, ensure).
The body never participates in the cache key. Two functions with identical intent blocks but different bodies have the same intent hash. Changing the body of an explicit declaration does not trigger a recompile.
Changing any intent clause changes the hash. Re-phrasing the intent causes a cache miss and re-prompts the model. This is by design: intent text is specification, and changing the specification should require the compiler to re-derive the implementation.
※ intent_hash depends on this shape (name + types + clauses)
fn classify (sample : 𝕎, known : [signature]) → signature | fault<no_match>
intent: 「identify the instrument…」
forbid: 「return a match if distance exceeds 0.65」
ensure: 「output is the unique nearest signature, or no-match」
※ intent_hash does NOT depend on the body
※ changing 「0.65」 to 「0.70」 WILL change the hash
§7 · The AST Safety Pass
Every body produced by the online compiler goes through an AST safety pass before being stored or used. The pass rejects bodies that contain:
- Calls to
eval,exec,__import__, oropen - Access to
__builtins__,globals(), orlocals() - Any name in the forbidden-name list
- Direct file system or network access outside declared
consumeschannels
A body that fails the AST pass is rejected with a diagnostic. The compiler retries with a corrective prompt (up to the configured retry limit, typically 3). If all retries fail, the hole remains unfilled and a compile error is raised.
§8 · The Approval Workflow
Every compiled artefact carries a Provenance record with six fields:
backend : anthropic ← offline | anthropic | snapshot | explicit model : claude-sonnet-4-5 ← model used for this compilation intent_hash : 7c9e3a2b4f8d1e6a ← stable over re-compilations with same intent body_hash : 9d4a7e2c1b3f6580 ← informational; not the cache key timestamp : 2026-04-24T18:32:11+00:00 accepted : False ← the only user-controlled field— provenance record · every compiled artefact carries one
Workflow states
| State | How you get there | Effect |
|---|---|---|
| Compiled, not accepted | Online compile completes. accepted: False | Body is available in the REPL session. Not persisted for future runs. |
| Accepted | User runs :accept amplify | Body written to snapshot store, keyed by intent_hash. Future runs in snapshot mode replay without API call. |
| Rejected | User runs :reject amplify | Body removed from snapshot store. Hole is open again. |
:accept is the gate. It converts a one-off online run into a pinned, deterministic, version-controllable body. Commit the snapshot file to lock the behaviour. CI runs in snapshot mode and never touches the network.
§9 · Entanglement Runtime
An entangled pair is a runtime object wrapping two values and a coupling function Φ. The invariant is:
For any transformation T applied to one side, the other side is automatically transformed by Φ(T).
Implementation lock
- Transformations go through the pair's
transform_left/transform_rightmethods - Direct mutation of
pair.leftorpair.rightbypasses the coupling and is a runtime error - Disentanglement (
disentangle(pair)) returns the two independent values; the coupling function is discarded - Entanglement is not transitive — A ▷◁ B and B ▷◁ C does not create A ▷◁ C
§10 · Drift Semantics
The drift operator ⇝ is defined componentwise on 𝕎. Given w = ⟨f, A, φ, σ⟩ and scalar δ:
| Component | After drift by δ | Behaviour at δ → ∞ |
|---|---|---|
| Frequency f | f + δ · A (amplitude-modulated shift) | Increases monotonically |
| Amplitude A | A · exp(−|δ|) | Decays to 0 |
| Phase φ | φ + δ · f (mod 2π) | Rotates continuously |
| Shape σ | lerp(σ, sq, |δ|) | Converges to square wave sq |
The square wave sq is the attractor of the drift operator. It is the fixed point: sq ⇝ δ = sq for all δ. Thermodynamic termination occurs when a process reaches sq and amplitude has decayed to zero.
§11 · Forbidden Names
The following names are forbidden in compiler-generated bodies, in python { } escape blocks (but not in extern python { }), and in any expression evaluated through the REPL expression evaluator:
eval exec __import__ open
__builtins__ globals() locals()
os.system subprocess socket
__class__ __base__ __subclasses__
Any use of a forbidden name in a compiler-generated body will cause the AST safety pass to reject the body. Any use in a python { } block will cause a compile error at the point of evaluation.
The extern python { } block is the deliberate exception — forbidden names are permitted there, because it is explicitly the "trust the human" escape hatch.