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.

amber>
\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 h

On the serving side the handlers are q's, and run when they should:

NameWhen
.z.pga sync message; its value is the answer (by default q's value, qSQL text too)
.z.psan async message
.z.po / .z.pca connection opens / closes (its handle as the argument)
.z.wthe handle of the message being handled
.z.tsthe 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.

amber>
.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 q

The 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

amber>
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 nothing

The 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.

amber>
\l hdb_data
count quotes                                  / 2364
select n:count i, avg bid by sym from quotes where date=2026.04.29

The 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.

Not there yet

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.