CX+AI

CX AI Manual

AI integration: providers, aifunc, markers, codeswap

GENERATED from DOCS/reference/manuals/CX_AI_Manual.html by tests/forge.cx — edit the source, not this page.

CX+AI — AI Surface (v02, V2.0.4+)

The AI features in v02. Replaces archive/AI_CX_SYNTAX_v1.x.md and archive/AI_OPCODES_v1.x.md.

⚠ HISTORICAL (PB v02 era, retired 2026-05-30). Sections §1–8 describe the PB-based v02 AI surface (arCodeModify, aifunc_ct/_rt, \{N: marker syntax, KwaAI library hooks, etc.). PB sources are in Backups/beta_v2.0.28_retirement_2026-05-30.7z. The active C v3 AI runtime is described in §9 below and in runtime/cx_ai_*.c. The provider/call builtins (ai_set_provider, ai_call, ai_init, generatecode, replacecode, etc.) still function in v3 via the C port of these modules; aifunc_ct/_rt and the \{N: marker syntax are also forward-ported — see DOCS/design/UNIFIED_REGISTER_ISA.md for the unified VM model they now run on.

The AI surface is shaped around a single architectural principle: AI-generated bytecode lives in a separate code array (arCodeModify); the original CX code in arCode stays pristine. Dispatch flips between the two via a small per-callee or per-marker stack. See ARCHITECTURE.md for the substrate. (Note: in v3/C era, arCodeModify is implemented via the register VM codeswap mechanism — see §9.)


1. Providers

ai_set_provider("anthropic");    // or "openai", "google", "mistral", "cohere",
                                  //    "xai", "deepseek", "groq", "ollama", "custom"
ai_set_key("sk-...");            // optional; env var is read otherwise
ai_init();                       // returns 1 if a key is resolvable
println(ai_get_error());         // last error string

The 9 cloud providers each read a conventional env var (ANTHROPIC_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, etc.). Ollama is local-but-HTTP. CxLlama (a daemon-driven local llama.cpp) is reserved as provider #9 but not yet wired in v02 — see "Deferred" at the end.

Provider fallback chain (V2.0.4)

#pragma ai_fallback "groq, ollama"   // at compile time
ai_set_fallback("groq, ollama");     // at runtime

When the current provider's call returns "ERROR: ...", AI_MakeRequestWithFallback walks the chain provider-by-provider. State (provider/model/key/endpoint) is snapshotted before the walk and restored on success — the fallback is a one-shot rescue, not a permanent switch.

Cache + async

key  = "task-signature-or-hash";
hit  = ai_cache_get(key);
if (hit == "") {
   result = ai_call(prompt);
   ai_cache_put(key, result, 3600);  // ttl seconds
} else { result = hit; }

req = ai_call_async(prompt);          // returns request id (or <0 on error)
while (ai_async_ready(req) == 0) { ... }
result = ai_async_result(req);        // blocks if still pending; auto-cleans up

2. The aifunc decl (V2.0.1, multi-param)

Declare a function whose body the LLM writes:

aifunc_ct function add.i(a.i, b.i) {
   "Return a + b."
}

aifunc_rt function clamp.i(x.i, lo.i, hi.i) {
   "Return x clamped to [lo, hi]."
}

// Call site looks identical to a regular function call:
result = add(3, 7);          // -> 10
y      = clamp(50, 0, 100);  // -> 50

The body is a single string literal = the LLM task. Both aifunc_ct and aifunc_rt are modifier keywords before function:

Multi-param: any number of int / string / float / long params. Mixed types route via gFuncParamTypes (per-funcId per-arg lane).

Dispatch — CXOP_CALLAI

A call to an aifunc emits CXOP_CALLAI fnEntryPC, nArgs, fnFirstSlot with funcId in flags. The handler:

  1. Copies args into the callee's absolute slot range (frameless — AI funcs in v02 are NOT framed; see ARCHITECTURE.md).
  2. Swaps gActiveArCodeKind to #ARCODE_MODIFY.
  3. Sets pc = entryPC in arCodeModify.

RETURN restores the caller's branch via stCallFrame\savedBranch (Phase 3b plumbing). Kept as a dedicated opcode (not a flag on CALL) so future hardware-accelerated AI runtimes can intercept this single opcode.

Iteration + momentum (V2.0.2)

#pragma ai_iterations 3       // refinement passes; round 1 generates, 2..N polish
#pragma ai_momentum   2       // 0=auto, 1=det, 2=balanced, 3=low-latency, 4=creative

aifunc_ct function maxof3.i(a.i, b.i, c.i) {
   "Return the largest of a, b, c."
}

Round-N prompts feed round-(N-1)'s response back to the LLM with explicit "improve, but stay within the listed opcodes (PUSHI, FETCH, STORE, ADD, ... RET)" — without that constraint, refinement tends to invent ops we don't support (DUP2, etc.).

Momentum → temperature: 0→0.7 if iter>1 else 0.0, 1→0.0, 2→0.5, 3→0.2, 4→0.9.

3. Codeswap markers (V2.0.3)

Inline regions where the default CX code runs first, and AI can replace the body at runtime via generatecode + replacecode.

#pragma named val
val.i = 0;

function runMarker.v() {
   \{5: "Set val to a value"
      val = 42;                     // default code, runs first
   \5:}
}

runMarker();
// val = 42

generatecode("Set val to 99. Push 99 then store into val.", 5);
replacecode(5);

runMarker();
// val = 99 — AI body in arCodeModify swapped in for marker 5

How it works

Nesting works — peer markers each have their own slot; nested markers stack via gMarkerCallStack. Inner is bypassed when outer is replaced (AI body doesn't contain MARKER_BEGIN ops).

Optimizer remap contract: any pass that touches gnBytecodeLen must remap gMarkers\beginPC/endPC through oldToNew[]. This is wired in V2.0.3 for both optimizer_pass_dead_first_store and optimizer_pass_peephole_fusion; new passes must opt in.

Marker builtins

BuiltinWhat it does
generatecode(task.s, N.i)LLM round-trip; stashes response in gMarkerCodeBuffer. Pass "" to reuse the marker's inline description as prompt.
replacecode(N.i)Parses gMarkerCodeBuffer into arCodeModify; registers + activates marker N's swap.
setcode(body.s)Stuffs gMarkerCodeBuffer with a CX-supplied string — bypasses the LLM. Lets you write deterministic offline tests. (V2.0.4)
clonemarker(srcId.i, dstId.i)Clones src's active swap-body to dst (both must be declared marker sites). "Stamp out N rival brains from one template." (V2.0.4)

Marker bodies are NOT functions: no params, no return value. The eval-stack must be in the same state at exit as at entry. Use FETCH name / STORE name to access globally declared #pragma named slots.

When AI writes RULE text: -> works there too (v3.187.0)

An aifunc can write a C-rule at runtime and hand it to arm() — an AI authoring entity behaviour as data. Rule text spells handle metadata exactly like every other CX program:

arm(e, "{ int n = e[\"skus\"]->count; ... }")     // the metadata surface, in a rule

There is nothing special to remember and nothing to translate. ->count, ->cap, ->type, ->valid and ->id all read the same in rule text as in program text, because the rule front-end lowers them through the same shared builder to the same call the sandbox already accepts.

The retired call spellings do NOT work here either. jsonSize(...), queueCount(q), listSize(xs) and the rest are CX-E1048 in rule text just as they are everywhere else (Language Reference §11). Until v3.187.0 rule text was the one exemption — because -> parses as a field read on a dereference and the rule sandbox declines that whole node class — so an AI trained on documentation older than v3.187.0 will write the call form. Regenerate the prompt, do not translate.

Why this matters more in a rule than anywhere else: a rule that fails to compile makes arm() return 0 and the rule simply never fires — there is no error at the call site. Wrong metadata spelling in rule text is behaviour that silently does nothing, in either direction.

What -> does NOT open in a rule. Only the metadata fields. e->hull is still refused (it is not a spelling of e["hull"]), pointers are still refused, and a metadata field on something that is not a container handle is still an error rather than a 0.

4. The library hooks (KwaAI)

; Modules/ai/cx_ai-v02.pbi
Procedure.l AI_LibraryLookup(name.s, task.s, mode.l)   ; today: return 0 (miss)
Procedure   AI_LibraryStore(name.s, task.s, mode.l, body.s, score.l)  ; today: no-op

Two pinch-points wired into AI_CreateFunc_v2 (the aifunc-decl materialiser). Today both are no-op stubs; the real subsystem will:

Why this matters — every AI feature above currently requires a paid API key. The library lets one developer's vetted output serve thousands of others without spending more tokens. Pinch-points are present so the existing AI features automatically benefit when the subsystem lands.

5. Legacy AI surface (still works)

The pre-aifunc-decl builtin form is preserved for backward compatibility:

#pragma named val
val.i = 0;

rt_createaifunc("setval42", "Set val to 42.");      // @todo VERIFY -- see note below
result = ai_call_func("setval42", 0);                // dispatches into arCodeModify

src.s = "PUSHI 99\nSTORE val\nRET\n";
ai_parse_bytecode("setval99", src);                  // hand-built bytecode
ai_call_func("setval99", 0);

println(ai_dump_asm("setval99"));                    // textual disasm
@todo — ct_createaifunc does not exist. Verified 2026-07-23 against runtime/cx_builtin_names.h and the compiler's special-form sites: the real family is rt_createaifunc / rt_callaifunc / rt_runaifunc / rt_destroyaifunc / rt_aifunc_count — all rt_-prefixed, no ct_ variant. The snippet above has been changed to rt_createaifunc, but the surrounding claim that this runs the LLM at compile time is unverified and may have been the reason a ct_ name was written. The compile-time path that definitely exists is the aifunc_ct declaration (§Hello, AI-Generated World in the Language Reference). Someone who knows this subsystem should confirm which of the two this example meant, rather than a guess being written in here.

rt_createaifunc / ai_call_func / ai_parse_bytecode / ai_set_named / ai_get_named / ai_dump_asm are all functional. New code should prefer the aifunc decl + markers, but cxtest 146 and Examples-legacy/AI/830-839 still exercise this path.

ai_ws_get / ai_ws_set are intentionally not ported — alfa itself deprecated them in V1.110.90 in favour of FETCH name / STORE name through gAINamedSlots. See memory entry project-ai-ws-skip.

6. Building offline / no-API tests

Use setcode + replacecode to drive markers without an API key:

function runM.v() {
   \{7: "default"
      val = 1;
   \7:}
}

runM();                                              // val = 1
setcode("PUSHI 99\nSTORE val\n");
replacecode(7);
runM();                                              // val = 99

For aifunc decls, you can plant the body via ai_parse_bytecode under the same name the decl uses internally (e.g. for aifunc_ct function mul.i(...) the body is registered as mul). But the decl path also tries to generate at compile time — so for tests, prefer setcode + replacecode + markers over the decl form.

7. Deferred

8. Where to look

9. C runtime layer (2026-05-29)

The AI subsystem lives in runtime/cx_ai_*: