amber-ai

A local, offline AI co-pilot for Amber. It reads your live tables before it answers — their names, column types, attributes and row counts — so its suggestions are about your session, not about a generic K tutorial.

v2.0.0 requires amber ≥ 1.9.5 127.0.0.1 only 184 tests GitHub →
amber>
amber> trades:gentq 1000000

amber> \ai average price per symbol in the last hour
select avg px by sym from trades where time>.z.p-01:00
amber>
amber> select last px by sym from trads
error[E0101]: Undefined variable `trads`

amber> \ai why
The name `trads` does not exist. This workspace has `trades`
(table: time:P sym:S`g px:F size:I, 1,000,000 rows) — you are one
character off. `sym` already carries the `g attribute, so the
grouped select will use the index.

Four things that make it different from a chat window

Local only

The one endpoint it ever contacts is http://127.0.0.1:11434 — a model server on your machine. No account, no API key, no telemetry, no TLS stack, no hosted fallback. Your schema and your queries do not leave the box.

It cannot hang your REPL

Every request carries an absolute millisecond deadline covering connect, send and the whole receive loop. Tab completion gets 100 ms; a failed connect arms a 4-second circuit breaker so a REPL with no backend pays one failed connect, not one per keystroke.

It cannot break your REPL

Every entry point is trapped. No backend, no model pulled, a malformed reply, a half-open socket — each is one friendly sentence, never an exception and never a stack trace.

It does not patch Amber

It installs through the published extension seam: three files into ext/, one into lib/, one rebuild. ./uninstall.sh puts the engine back byte-for-byte.

Install

git clone https://github.com/BonucciAndrea/amber-ai.git
cd amber-ai
./install.sh /path/to/amber

The path is optional — ./install.sh finds a sibling ../amber, $AMBER_HOME, ~/amber, or an amber on your PATH. It then checks your toolchain, checks the target's version and extension ABI (and refuses politely if they do not match), copies src/net.c, src/net.h and src/ai_ext.c into ext/ plus lib/ai.k into lib/, runs the engine's own build.sh, verifies the `ai / `aio verbs and \ai against Amber's own suite, and probes 127.0.0.1:11434 — distinguishing nothing listening, a live Ollama, a live Ollama with no models pulled, and something else squatting on the port, each with its own fix.

Then start a model

Any local backend that speaks Ollama, llama.cpp or an OpenAI-compatible /v1 endpoint will do:

curl -fsSL https://ollama.com/install.sh | sh    # linux / wsl2  (macOS: brew install ollama)
ollama serve &
ollama pull qwen2.5-coder:0.5b                   # ~400 MB, runs on a laptop CPU

Or run ./setup-ollama.sh, which walks through the same thing and asks before every system change. Without a backend the agent is a no-op: it says so once, and the REPL is exactly Amber.

Shell alias

alias amber-ai='AMBER_NATIVE=1 AMBER_AI=1 \
  AMBER_AI_URL="http://127.0.0.1:11434/api/generate" \
  AMBER_AI_TIMEOUT_MS=10000 "$AMBER_HOME/a"'

alias amber-noai='AMBER_AI=0 "$AMBER_HOME/a"'    # agent off for one session
Do not pass lib/ai.k as an argument

amber $AMBER_HOME/lib/ai.k runs the agent library as a script and exits. repl.k already loads it through lib/ext.k at startup; the alias only has to start the REPL. And alias the launcher a, not the bare amber binary — the binary has no stdlib and no repl.k, so \ai does not exist there.

\ai commands

CommandWhat it does
\ai <question>schema-aware code generation for this session
\ai explain <expr>unpack a terse K expression, innermost first
\ai why [error]diagnose the last (or a given) error against your real tables
\ai profile <table>rows, column types, attributes, distinct counts + indexing advice
\ai optimize <query>faster vector paths for a query
\ai statusendpoint, model, budgets, what it has learned, live workspace
\ai memory / learn / forgetinspect / extend / erase the persistent memory
\ai on | offmaster switch (default on)
\ai tab on | offinline Tab suggestions (default on)
\ai model <name> | url <endpoint>switch model / endpoint for this session
\ai timeout <ms> | tabtimeout <ms>answer / Tab budgets
\ai warmload the model now, on its own budget, and measure your machine's tokens/sec
\ai retryforget a dead endpoint and try it again now
\ai servestart a local ollama in the background (hosts without systemd)

\ai profile and \ai why read your actual workspace first — the row counts, types and attributes in their output are measured, not generated.

Vector execution: Tab is layered so the network is last

And usually never reached at all.

1

Lexical

Globals in your workspace, table column names, \ commands, \ai sub-commands, the Amber/K vocabulary. Instant, offline. (Amber's own — present without this package.)

2

Memory

Whole lines you have actually run before, mined from ~/.amber_ai_memory.k. Instant, offline, and what makes the REPL feel like it has learned your habits.

3

Model

Only when 1 and 2 find nothing, only when the agent is on, and only inside AMBER_AI_TAB_MS (default 100 ms).

A model suggestion is rendered as dim ghost text after the cursor and is never inserted until you accept it with a second Tab (or , or Ctrl-F). Any other keystroke discards it. Accepted suggestions are recorded as feedback.

amber> select avg px by sym from tr█ades where sym=`AAPL      <- dim = suggested, not typed

Persistent memory

A local model cannot be fine-tuned while you type, so "learning" here means persistent few-shot context. Every line that ran cleanly, every schema this workspace has held and every accepted Tab suggestion is appended to ~/.amber_ai_memory.k, one self-describing Amber expression per line:

~/.amber_ai_memory.k
ai.rec["q";1755512345;3;"select sym,px from trades";"trades quotes"]
/       kind  epoch-s  hits  text                    context

Loading it is simply evaluating it, so it stays human-readable and hand-editable. \ai memory shows it, \ai forget erases it, AMBER_AI_MEMORY=/path moves it (per-project memories work), and deleting the file is always safe.

Tuning the budget

A 7B on CPU generates a few tokens a second, so a 128-token answer can take 30 s while a 0.5B does it in 4 — and Ollama unloads an idle model after five minutes, so the load is paid again and again inside the same deadline.

amber>
amber> \ai warm            # load the model now, on its own budget, and measure

\ai warm asks for a single token, so it returns as soon as the weights are resident rather than when an answer is finished. It then times a short generation and, if your measured tokens/sec cannot produce a full answer inside the current budget, raises the budget and says so. It measures your machine rather than guessing from the model's name. Every request also carries keep_alive, so the backend holds the model for 30 minutes after each call.

VariableDefaultEffect
AMBER_AI_URL127.0.0.1:11434the model endpoint
AMBER_AI_TIMEOUT_MS10000answer budget
AMBER_AI_TAB_MS100Tab budget — leave it small, it runs on the keystroke path
AMBER_AI_NUM_PREDICT128generation length; the biggest single lever after the load
AMBER_AI_KEEP_ALIVE30m"0" unloads at once, "-1" keeps forever
AMBER_AI_MEMORY~/.amber_ai_memory.kwhere the persistent memory lives

Each command scales off AMBER_AI_NUM_PREDICT rather than using it directly, because a one-line answer and a table profile do not need the same room:

CommandTokens at the 128 default
\ai, \ai explain1.5× — 192
\ai why2× — 256
\ai profile, \ai optimize3.5× — 448

Setting AMBER_AI_NUM_PREDICT=48 scales all three down together and roughly halves the wait. \ai warm sizes the answer budget from the longest of them, so raising the caps cannot move the timeout to \ai profile.

The engine repository contains no AI code and no network code

grep -r socket src/ in the engine finds only src/0.c's IPC support, which predates 1.9 and is the same code kdb-style hopen uses. Everything on this page lives in ext/, and ./uninstall.sh removes it.