█████
░░███
░███ █████ ██████ ████████ ██████
░███░░███ ███░░███░░███░░███ ░░░░░███
░██████░ ░███ ░███ ░███ ░███ ███████
░███░░███ ░███ ░███ ░███ ░███ ███░░███
████ █████░░██████ ████ █████░░████████
░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░
A coding agent in Java 21. No framework, no orchestration library, no magic — a loop, six tools, and a language model with opinions.
Watching an agent chain file reads together, spot a bug and fix it feels like watching something think. Underneath, it is this:
- Send the conversation — all of it, every time — to the model, along with descriptions of the tools it is allowed to call.
- If the model answers with text, print it. Done.
- If it answers with a tool call instead, run the tool, append the result to the conversation, and go back to step 1.
That is Agent.respond(). Nobody taught the model to list a directory before reading a file, or
to re-read a file after a failed edit. That behavior emerges from the loop and the tool
descriptions alone.
| Tool | What it does | Guardrail |
|---|---|---|
list_files |
Snapshot of a directory | Capped at 200 entries |
read_file |
Return a file's contents | Capped at 100 KB |
edit_file |
Exact-match string replacement (creates the file when old_str is empty) |
Refuses ambiguous matches |
delete_file |
Remove a file | Refuses a directory |
run_command |
Run a shell line in the project directory | Stopped by the timeout, default 600 seconds |
plan |
Record the steps of the work, and give the list back | Capped at 20 steps of 200 characters |
A tool is a name, a description the model reads, a JSON schema, and a function that runs when the model asks for it. Adding a seventh means writing one class and registering it.
konacode reads and writes inside this project with no question. A call to plan records the steps
of the work, and it reaches nothing outside the session, so that call needs no question either. For
a read or a write outside this project, and for a command, a judge decides. The judge is a second agent. It reads the name of the
tool, what the call does, what the call acts on, where the project is, and the message you typed,
and it answers allow, ask or deny. It answers ask when it is not sure, and konacode then puts the
question to you. In the rich interface the question looks like this:
read_file wants to read outside this project.
/etc/passwd
y read it once
n refuse
a always, for read_file in /etc
Answer y, n or a. y runs the call once. n refuses it, and konacode asks again the next
time, because one answer covers one call. a runs it, and every later call the same tool makes in
the same folder, for the rest of this session. esc refuses and stops the turn. A pipe cannot ask a
question, so it refuses every call outside this project, and every command, that the judge does not
allow.
A call inside this project reaches no judge. konacode reads, writes and deletes a file inside this project with no question and no judgement, in a terminal and in a pipe. The judge sees a read or a write outside this project, and a command. Read that sentence before you trust a piped session.
konacode prints a judged: line for every call the judge answered, because a call the judge allowed
runs with no question. When the judge denies a call, konacode refuses it and tells the model why.
/policy shows or changes what konacode asks:
/policy allow-all allow every call
/policy effect ask about every read and write outside this project, and every command
/policy judge ask the judge, and ask you about what it does not clear
judge is the setting konacode starts with, in a terminal and in a pipe.
run_command asks about the command line, and a then covers that exact line. A line that holds
$, `, *, ?, [ or ~ means something else on another day, so konacode offers y and
n only.
A skill is a folder inside ~/.konacode/skills/, with a SKILL.md file that names it and
describes it.
/skill list the skills
/skill commit-message load one into the conversation
/skill lists every skill, with its description. /skill <name> loads one skill into the
conversation. You can load several skills at the same time, and /clear removes them all. The
model reads a reference file in the skill folder with read_file, only when it needs it.
0.1.0 was the loop, three tools and two interfaces. The loop is the same. Everything 0.2.0 adds is a control layer around it.
- konacode asks before it acts outside this project. A tool states what one call does, a
policy decides, and the loop asks you.
aremembers the answer for the session. - A judge answers first. A second agent reads every question the policy writes and answers allow, ask or deny. A piped session is no longer wide open.
- Two more tools:
delete_fileandrun_command. escstops a turn, and the tools stop between two steps of their work.- konacode says what it did. Nine kinds of trace event, on the screen with
/traceand in a file in~/.konacode/traces/. - Skills.
/skillloads reusable instructions from~/.konacode/skills/.
CHANGELOG.md has the whole list.
You will need Java 21 and an OpenAI API key.
Take the jar from the latest release:
OPENAI_API_KEY=sk-... java -jar konacode.jarOr build it:
git clone git@github.com:bbossola/konacode.git
cd konacode
mvn package
OPENAI_API_KEY=sk-... java -jar target/konacode.jarThen chat:
Chat with konacode (use 'ctrl-c' to quit)
You: what does this project do?
tool: list_files({"path":"."})
tool: read_file({"path":"pom.xml"})
tool: read_file({"path":"src/main/java/dev/konacode/agent/Agent.java"})
konacode: It's a Java CLI agent that answers questions by calling tools...
Every tool: line is the model deciding, on its own, that it needs more context before it can
answer.
Things worth trying:
- "Find the TODO comments in this codebase and tell me which one looks most urgent."
- "Write a Java class that plays an emoji guessing game, then improve the hints."
- "Rename this method across the project" — and watch it chain list → read → edit without being told to.
| Variable | Required | Default |
|---|---|---|
KONACODE_AUTH |
no | key |
OPENAI_API_KEY |
with KONACODE_AUTH=key |
— |
KONACODE_MODEL |
no | gpt-5-mini |
KONACODE_JUDGE_MODEL |
no | the value of KONACODE_MODEL |
KONACODE_BASE_URL |
no | https://api.openai.com/v1 |
The judge uses the same credential and the same base URL. It runs on every call outside this project and on every command, so a large main model can have a small fast judge.
KONACODE_AUTH=codex uses a ChatGPT subscription in place of the key. See
A ChatGPT subscription. The defaults then change: the base URL is
https://chatgpt.com/backend-api/codex, and the model is gpt-5.5.
Plus six system properties.
| Property | Values | Purpose |
|---|---|---|
konacode.maxIterations |
a whole number, default 8 |
the ceiling on tool calls for one message |
konacode.maxIterations.whenPlanning |
a whole number, default 24 |
the ceiling for a turn in which the model records a plan |
konacode.ui |
auto, plain, rich, default auto |
which interface to use |
konacode.trace |
off, basic, full, default off |
how much the trace file records |
konacode.trace.maxFiles |
a whole number, default 100 |
how many trace files konacode keeps |
konacode.command.timeoutSeconds |
a whole number, default 600 |
how long one command may run |
konacode looks for a terminal. It uses the rich interface when it finds one, and the plain interface otherwise. A pipe therefore gets the plain interface.
The rich interface gives line editing and history, with the arrow keys, ctrl-a, ctrl-e and
ctrl-r. It saves the history in ~/.konacode/chat_history. Press alt-enter to add a second
line, and enter to send. It renders markdown, and it turns a spinner while the model thinks.
Press esc to stop a turn. konacode stops at the next safe point, prints Stopped., and gives
you the prompt back. The conversation keeps what happened, so you can then ask konacode to undo
what it did.
ctrl-c behaves as it always has, and esc did not change it: at the prompt it clears the line,
and during a turn it ends konacode. ctrl-d quits.
Eight commands work in both interfaces.
| Command | Action |
|---|---|
/help |
show the commands |
/tools |
show the tools the model can call |
/skill |
show the skills, or load one by name |
/policy |
show or set what konacode asks before it acts |
/trace |
show or set how much the screen reports |
/clear |
forget the conversation and start again |
/compact |
replace the conversation with a summary the model writes |
/exit |
end the session |
Set konacode.trace=basic or konacode.trace=full and konacode writes a trace of the session
to ~/.konacode/traces/, one JSON line for each event. The default is off, and konacode then
writes no file. basic records the loop, the times, the outcome of each turn and the token
counts. full adds the request and the reply, so you can replay a call. /trace basic shows the
same events on the screen while the session runs, whether or not the file is on.
KONACODE_BASE_URL points at any OpenAI-compatible endpoint, so a local model works with no
code changes. With Ollama:
ollama pull qwen2.5-coder:32b
export KONACODE_BASE_URL=http://localhost:11434/v1
export KONACODE_MODEL=qwen2.5-coder:32b
export OPENAI_API_KEY=ollama # required non-empty; Ollama ignores it
java -jar target/konacode.jarPick a model that is genuinely good at function calling — qwen2.5-coder and qwen3-coder both
are. Smaller general-purpose models will emit a single tool call and then fail to chain, which
is the one thing an agent needs them to do well.
A Claude Pro or Max subscription cannot be used here. Anthropic bans the OAuth token of a Free, Pro or Max account in every third-party tool since 4 April 2026, and konacode is one.
A ChatGPT subscription pays for the Codex CLI, and konacode can use the same login.
codex login # once; it writes ~/.codex/auth.json
export KONACODE_AUTH=codex
java -jar target/konacode.jarkonacode reads the token that codex login wrote, and it speaks the Responses API of the Codex
backend. The default model is gpt-5.5. KONACODE_MODEL picks another one; codex shows the
list your plan allows. When the token is stale, konacode prints one line that says to run
codex login again. konacode refreshes nothing, and it writes nothing into that file.
Four things to know before you use this:
- No page we could read permits or prohibits it. OpenAI staff said in public that a subscriber may use the subscription in the tool they prefer, for personal use of their own subscription. A pooled account or a shared credential is not that. Read the terms yourself.
- The endpoint has no SLA, and it can change with no notice.
- OpenAI recommends an API key for production work.
- This route can close. Anthropic tolerated the same thing, then banned it on 4 April 2026.
OPENAI_API_KEYstays the supported route.
konacode names itself in the originator and User-Agent headers. It never claims to be the
Codex CLI.
src/main/java/dev/konacode/
├── cli/ the two interfaces, the loop, the commands
│ └── markdown/ the renderer
├── agent/ the conversation and the tool loop
├── tools/ the Tool interface, the registry, the five tools
├── policy/ what the agent is allowed to do
└── llm/ the provider interface
├── http/ the transport, and the codec and credential contracts
└── openai/ what OpenAI wrote: two codecs, two credentials
Dependencies run strictly downhill: cli → agent → {llm, tools, policy}.
ARCHITECTURE.md diagrams how a turn actually runs. Three things are
interfaces with a default implementation rather than hardcoded — the tools, the LLM provider,
and the tool policy — so extending any of them is a new class rather than a rewrite. The
conversation is a plain class, because reading all of the history and writing all of it back
covers every change to it. CLAUDE.md defines every element; CONTEXT.md records why the
design is shaped this way.
A loop where the model decides when the loop ends deserves some skepticism. konacode keeps things honest with:
- A ceiling on tool iterations per user message, so a confused model cannot run without an
end. The
plantool raises the ceiling of one turn, and konacode puts it back at the start of the next turn. Both numbers are yours:konacode.maxIterationsandkonacode.maxIterations.whenPlanning. - Output caps on reads and listings, so one stray
target/does not flood the context window — or the bill. - Unambiguous edits only —
edit_filefails loudly rather than guessing when the search string matches more than once. - A policy consulted before every tool call. The default policy allows a read and a write
inside this project, and it asks a judge about everything else. A call to
planreaches nothing outside the session, so no judge reads it. See Approval.
Production agents add sandboxing, token budgets, rate limiting and permission prompts on top. Same skeleton, more armor. FOLLOWUP.md tracks what is coming.
MIT.
Inspired by Nimbo by Gio Scalzo, and its companion essay Demystifying AI Coding Agents in Swift.