JACA
PydanticAI backend, first-party Go TUI, JSON-over-stdio between them. Strict runtime, thin renderer, sessions that survive reboots.
Install
JACA ships as a Python-first tool with a bundled Go binary. The
fastest path is uv — one command drops five executables
on your PATH (jaca, jaca-go,
jaca-read-only-worker, and two underlying entrypoints).
$uv tool install just-another-coding-agent Run on macOS, Linux, or Windows via WSL2. Native Windows is not recommended yet — installs are rough at this point. If you're on Windows, use WSL2.
Needs uv on your machine first — grab it from
docs.astral.sh/uv
(one-liner for Linux, macOS, and Windows).
Wheels bundle the Go binaries, so no Node and no Go toolchain on
your end. Once installed, jaca is on your PATH — run
it from any directory.
First run
On first launch JACA opens a centered provider chooser — ChatGPT
subscription, OpenAI API key, or Anthropic API key — each labeled
with its setup state. Pick a lane, finish the OAuth round-trip in
your browser, and the model is set. Secrets land in the OS keychain
by default, with ~/.jaca/auth.json as fallback. Nothing
about the auth moment is captured in the transcript.
The low-friction lane is the point: if you already pay for ChatGPT, you can authenticate in-browser and start using JACA without first provisioning API credentials just to try the agent.
Architecture
One binary, two processes. The Go TUI renders; the Python runtime
owns meaning. They talk JSON-over-stdio — the same transport JACA
uses in headless mode
(e.g. jaca --headless --model openai-responses:gpt-5.4),
which means
the TUI is just another RPC client.
Steering, mid-run
A run is a pipeline, not a turn. While tools are executing, you can
queue the next instruction without waiting. Enter stages
a steering message for the next tool phase; Tab queues a
follow-up for end of turn; Esc interrupts the current
step and promotes queued steering. The composer shows you exactly what
is pending — in the clip below, two queued messages stack on top of a
running pytest: ↳ run go tests as well after the current
phase, and ↳ explain compaction w.r.t this repo at end
of turn.
- tab
- while streaming, queue a follow-up for end of turn; otherwise commit slash suggestions
- enter
- queue the next steering message for after the current tool phase
- esc
- interrupt the active run; backend promotes any queued steering
- ctrl-c
- first press — safe. second press — quit.
- /login
- connect ChatGPT subscription or set up an API key
- /model <catalog-id>
- switch model — e.g. openai-responses:gpt-5.4
- /session
- show current session id, name, fork parent
- /name <text>
- set or rename the active session
- /trace off|local|logfire
- observability mode
Tool contract
Every tool declares its concurrency class at registration. The registry enforces it; the runtime has no heuristics. Read-only tools run in parallel, everything that mutates state runs one at a time.
- read
- grep
- find
- ls
- write
- edit
- shell
- subagent
Sessions
Sessions are append-only and scoped to a workspace. Resume one with
jaca resume <id-or-name>, or run bare
jaca resume for an interactive picker. Forking is a
first-class operation: jaca fork <id> --name <new>
branches the conversation without mutating the parent, and the
lineage stays attached.
Stored at ~/.jaca/sessions/. When a resumed session gets
close to the active model limit, JACA compacts before the next run:
it keeps a bounded recent user-message tail, appends one
assistant-style summary message, then replays that replacement
history plus later native messages. The append-only session file
stays intact for resume and forensics. See
compaction architecture
.
Proof
agent journey / dashboard
Open the evaluation dashboardThe full run-by-run record behind the submission: slice history, sampled scanfield, and the task matrix that produced the score.
Before the Go worker landed, Terminal-Bench containers were still
paying subprocess startup on every read,
grep, find, and ls call, so
timeout pressure showed up as a real Harbor failure bucket rather
than edge noise. The deltas below are from the first broad post-worker
GLM-5 high A/B/C window.
Public submission running GLM-5. 15 jobs, 445 trials, 89 unique tasks, minimum 5 trials per task, validated end-to-end.
Weighted mean up 16%, total errors down 23%, AgentTimeoutError down 18% in the first broad GLM-5 high A/B/C window after the Go worker shipped into the benchmark path.
Replacing subprocess-per-call with a long-lived Go worker for read / grep / find / ls. Rust variant measured ~600×. Go won on repo fit.
See also: Terminal-Bench 2 submission ↗ · Go worker write-up