You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
jserve made jlib an inference server a coding harness can drive (#218, #220): aider works against it end to end, in its whole edit format. The
harness on the other side of that wire is somebody else's program.
jcode is jlib's own: an app in jlib/apps that reads a project, asks a model
for an edit, applies it, runs the build, and goes round again -- talking to jserveover HTTP, not linking ai::engine.
Why over the wire rather than in-process
jchat and jalpaca already link the engine directly. A third in-process
driver would prove nothing new and would pay a model load per run.
It puts both halves jlib has built -- the HTTP client and the async server --
on one wire, in the one place where a keep-alive failure is measurable.
What already exists for it
ai::openai -- the wire format as JSON and nothing else, tested with no
socket and no GGUF (ai_openai_test).
net::http client, util::http framing (with the suspending twins), and a
server that does keep-alive, chunked output and SSE.
sys::run(argv, out, err) -- a subprocess with no shell in between,
which is what a harness that runs a build needs.
sys::Directory, util::json, ai::chat, and jchat as a precedent for
the loop shape.
What it needs that does not exist
Roughly in dependency order.
The client half of ai::openai. Today the namespace parses a request and serialises a response, because it was written for the
server. jcode needs the mirror image: build a request body, and read back
either a whole chat.completion or a stream of chat.completion.chunks.
Same file, same test, still no socket -- and then the two halves can be
round-tripped against each other, which neither can do alone.
A client that streams, and that reuses a connection.net::http::request()
reads the whole body and returns, with a 1 MiB cap and no connection pool --
its header says so, deliberately. An SSE reply is a body you must read as
it arrives, and a harness makes many calls. aider opens a connection per LLM call, even though it asks for keep-alive #221 is the same measurement
from the server's side: the handshake per call is real, and it is the client
that decides whether to pay it.
An edit format, and applying it. Start where the coverage already is: whole -- a filename and a fenced block -- because it needs no tool calling
and is the format aider was verified against on this server. A
search/replace format is the next step and is where the interesting failures
live: an anchor that does not match is precisely the harness lie the
server arc kept catching, and here the lie would be "applied" when nothing
changed.
Context selection. Which files go into the prompt, and what happens when
they do not fit. The context length is in the GGUF and jserve knows it;
the harness either asks or is told, and guessing is how a reply silently
loses its head.
Tool calling: the honest part
A "full" coding harness in 2026 means tool calls, and jlib cannot make one
today at three separate levels:
openai::request deliberately does not model tools/tool_calls; it
records that it was asked (wants_tools) so a server can refuse loudly
rather than answer prose.
ai::chat does not render a tool list into the model's own template.
nothing constrains decoding, so a schema is a request rather than a
guarantee.
Each of those can be wrong on its own, so each is its own piece of work. The
order that makes sense is the one aider proved: a useful edit loop runs
without tool calling, so build that first and file tool calling as its own
arc afterwards -- with the measurement of what it buys taken against a jcode
that already works.
What would test it
Most of it needs no model. Point jcode at tests/httpserver.hh
replaying a recorded completion and assert the edit lands on disk in a temp
tree. That is the same in-process-server shape the rest of the tree uses, and
it is the only way to test the replies nobody would send on request -- a
fenced block naming a path outside the root, a stream that stops mid-chunk.
The break-the-guard rule applies double here. A coding harness's entire job
is to report that it changed something; every assertion about a file being
written has to be broken on purpose once and watched to fail.
What it is deliberately not
Not an agent with a shell. Not something to point at a repository it may
commit to unasked. One project root, every write announced, and the build
command it is allowed to run named rather than inferred.
This paragraph is here for the same reason the one at the top of http_server.hh is: the way a narrow thing becomes a broad one is by nobody
writing down that it was meant to be narrow.
Sub-issues
Each numbered item above becomes its own issue when work starts on it; this one
is the shape and the order.
jservemade jlib an inference server a coding harness can drive (#218,#220): aider works against it end to end, in its
wholeedit format. Theharness on the other side of that wire is somebody else's program.
jcodeis jlib's own: an app injlib/appsthat reads a project, asks a modelfor an edit, applies it, runs the build, and goes round again -- talking to
jserveover HTTP, not linkingai::engine.Why over the wire rather than in-process
jchatandjalpacaalready link the engine directly. A third in-processdriver would prove nothing new and would pay a model load per run.
jserveloads once and answers many; a harness makes many calls, which isthe whole reason jserve: convert to the async server, so a coding harness keeps its connection #218 made it async.
jcodealso works against any other endpoint,and the two can be compared on the same task.
jservegets its first in-tree client, which is exactly the gap http::server: nothing automated drives jserve, because it needs a model file #236records.
on one wire, in the one place where a keep-alive failure is measurable.
What already exists for it
ai::openai-- the wire format as JSON and nothing else, tested with nosocket and no GGUF (
ai_openai_test).net::httpclient,util::httpframing (with the suspending twins), and aserver that does keep-alive, chunked output and SSE.
sys::run(argv, out, err)-- a subprocess with no shell in between,which is what a harness that runs a build needs.
sys::Directory,util::json,ai::chat, andjchatas a precedent forthe loop shape.
What it needs that does not exist
Roughly in dependency order.
The client half of
ai::openai. Today the namespace parses arequest and serialises a response, because it was written for the
server.
jcodeneeds the mirror image: build a request body, and read backeither a whole
chat.completionor a stream ofchat.completion.chunks.Same file, same test, still no socket -- and then the two halves can be
round-tripped against each other, which neither can do alone.
A client that streams, and that reuses a connection.
net::http::request()reads the whole body and returns, with a 1 MiB cap and no connection pool --
its header says so, deliberately. An SSE reply is a body you must read as
it arrives, and a harness makes many calls. aider opens a connection per LLM call, even though it asks for keep-alive #221 is the same measurement
from the server's side: the handshake per call is real, and it is the client
that decides whether to pay it.
An edit format, and applying it. Start where the coverage already is:
whole-- a filename and a fenced block -- because it needs no tool callingand is the format aider was verified against on this server. A
search/replace format is the next step and is where the interesting failures
live: an anchor that does not match is precisely the harness lie the
server arc kept catching, and here the lie would be "applied" when nothing
changed.
Containment. A harness writes files and runs programs. Both want a root
they cannot escape, resolved rather than inspected -- the
files()lesson from net: serve a directory, and refuse everything outside it by resolving the path rather than inspecting it #225 -- plus a confirm gate that can be turned off on purpose
rather than by default, and a dry run that prints the edit instead.
Context selection. Which files go into the prompt, and what happens when
they do not fit. The context length is in the GGUF and
jserveknows it;the harness either asks or is told, and guessing is how a reply silently
loses its head.
Tool calling: the honest part
A "full" coding harness in 2026 means tool calls, and jlib cannot make one
today at three separate levels:
openai::requestdeliberately does not modeltools/tool_calls; itrecords that it was asked (
wants_tools) so a server can refuse loudlyrather than answer prose.
ai::chatdoes not render a tool list into the model's own template.guarantee.
Each of those can be wrong on its own, so each is its own piece of work. The
order that makes sense is the one aider proved: a useful edit loop runs
without tool calling, so build that first and file tool calling as its own
arc afterwards -- with the measurement of what it buys taken against a
jcodethat already works.
What would test it
jcodeattests/httpserver.hhreplaying a recorded completion and assert the edit lands on disk in a temp
tree. That is the same in-process-server shape the rest of the tree uses, and
it is the only way to test the replies nobody would send on request -- a
fenced block naming a path outside the root, a stream that stops mid-chunk.
*_live_testdrivingjcodeagainst a realjserve, which is themirror of what http::server: nothing automated drives jserve, because it needs a model file #236 asks for on the server side, and wants a GGUF for the
same reason.
is to report that it changed something; every assertion about a file being
written has to be broken on purpose once and watched to fail.
What it is deliberately not
Not an agent with a shell. Not something to point at a repository it may
commit to unasked. One project root, every write announced, and the build
command it is allowed to run named rather than inferred.
This paragraph is here for the same reason the one at the top of
http_server.hhis: the way a narrow thing becomes a broad one is by nobodywriting down that it was meant to be narrow.
Sub-issues
Each numbered item above becomes its own issue when work starts on it; this one
is the shape and the order.