Talking to q
Since 2.9.0 Amber speaks kdb+'s wire format, both ways. A q process can query an Amber one and the other way round, Amber replays q's logs, and it reads and writes the databases q writes. The test for all of it was a real kdb+ options tickerplant (feed, tp, rdb, cep, hdb, gateway, monitor), and it runs on Amber with its .q files unchanged.
Handles
Same as q: open a port, open a handle, send text or a function and its args. A negative handle sends async.
\p 5000 / listen on this machine (or start with amber -p 5000)
h:hopen 5000 / or `:host:5000
h"1+1" / sync: 2
h(`f;1;2) / a function by name and its args
h({x*y};6;7) / or the function itself: 42
neg[h]"x:42" / async, no answer
neg[h](`getq;`ibm);r:h[] / async, then wait for the next message (a deferred reply)
@[h;"1+`a";{"ERR ",x}] / a remote error comes back as q's: "ERR type"
hclose hOn the serving side the handlers are q's, and run when they should:
| Name | When |
|---|---|
.z.pg | a sync message; its value is the answer (by default q's value, qSQL text too) |
.z.ps | an async message |
.z.po / .z.pc | a connection opens / closes (its handle as the argument) |
.z.w | the handle of the message being handled |
.z.ts | the timer: \t 100 calls it every 100 ms, \t 0 stops it |
Who gets in
The checks are q's: .z.pw gets the user (a symbol) and the password and says yes or no, and
-u file / -U file hold user:password lines, the password as text or its md5.
A no is 'access at the client, as in q.
One thing differs, on purpose. \p 5000 listens on this machine only, where q takes every interface. Give a
host to open it wider, \p 0.0.0.0:5000 or -p 0.0.0.0:5000. A port open like that with no
.z.pw, -u or -U prints one line saying so, since anyone who can reach it can run code.
.z.pw:{[u;p] p~"secret"} / say yes to the right password
\p 0.0.0.0:5000 / and open the port to the network
h:hopen`::5000:bob:secret / a client, as in qThe bytes
-8!x gives q's bytes for x and -9! reads them back, so what you get is what q sends. Amber's own
compact format, the one peach and column files use, is `ser / `des now.
Logs
lf:`:tp_logs/log_2026.10.13
lf set () / a new, empty log (q's header)
lh:hopen lf
lh enlist(`upd;`quotes;x) / append a message, as a q tp does
-11!lf / replay it: upd runs for each, gives the count
-11!(100;lf) / just the first 100
-11!(-2;lf) / count the good ones, run nothingThe format is q's, so a log either side wrote replays on the other.
kdb+ databases
\l db loads a database kdb+ wrote: its column files, its enumerated symbols and sym file, string and other
nested columns (2.9.1), partitions and all, and the splayed tables next to them, even one whose .d files went missing (git won't keep dot files unless you ask). When Amber writes into a
database like that, with .Q.dpft or set, it writes q's own files, so q keeps reading it.
count, cols and tables see a partitioned table the way q does, and \l dir
goes into the directory, so \l . reloads after an rdb's end of day.
\l hdb_data
count quotes / 2364
select n:count i, avg bid by sym from quotes where date=2026.04.29The console of a serving process
A script that leaves a port or a timer open keeps running after it loads, as q's does, and reads its console a line at a
time. So rlwrap works on it, what the handlers print and what you type don't tread on each other, tables show cut to the
window, and an error in a timer or a handler is one line, '2026.10.13T14:20:00.000 type, not a report.
Compressing messages on the way out (Amber reads q's compressed ones), TLS, websockets and the HTTP handlers.
-u doesn't fence clients into the start directory as q's does, and \u doesn't reread the file.