The self-evolving agent that builds its own toolbox — and orchestrates it as a workflow.
JForge is a two-layer orchestration engine: a Supervisor decomposes your goal into a structured workflow plan, and a Router decides how to execute each step — forging new Java tools, reusing cached ones, searching the web, or answering conversationally. Complex multi-step tasks run automatically, with parallel execution and result chaining between steps.
- How It Works — 30-second Overview
- Installation
- Architecture
- CLI Options
- Usage & Example Prompts
- Troubleshooting
graph TD
User([User Prompt]) --> Supervisor{Supervisor Agent}
Supervisor -- "SIMPLE bypass\n(no plan needed)" --> Router
Supervisor -- "WORKFLOW\n(1 step)" --> Exec1[WorkflowExecutor\nSingle Step]
Supervisor -- "WORKFLOW\n(multi-step)" --> ExecN[WorkflowExecutor\nParallel Layers]
Exec1 --> Router
ExecN --> Router
Router{Router Agent} -- SEARCH --> Searcher[Searcher Agent]
Searcher -- Grounding Data --> Router
Router -- CREATE --> Coder[Coder Agent]
Router -- EDIT --> Coder
Router -- EXECUTE --> JBang[JBang Engine]
Router -- DELEGATE_CHAT --> Assistant[Assistant Agent]
Coder -- Generated Code --> Validate{Gate 1\nValidation}
Validate -- Invalid\nreject & retry --> Router
Validate -- Valid --> Save["tools/*.java"]
Save -- CREATE only --> Tester[Tester Agent]
Tester -- TEST PASSED --> JBang
Tester -- TEST FAILED\nauto-heal --> Router
Save -- EDIT --> JBang
JBang -- Success --> Guardrail{Output Guardrail}
Guardrail -- Files in tools/ --> Products[products/]
Guardrail -- Clean --> Resolved([Step Resolved])
JBang -- Failure --> Router
ExecN -- All steps done --> Summary([Task Resolved])
Supervisor -- Replan on failure --> ExecN
SDKMAN is the easiest way to manage JDK versions on Linux, macOS, and Windows (WSL/Git Bash).
Install SDKMAN:
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"Install Java 26 (or the latest available):
# List available Java 26 builds
sdk list java | grep "26"
# Install the GraalVM or Temurin distribution (example):
sdk install java 26-tem
# Set it as default
sdk default java 26-tem
# Verify
java --version
# Expected: openjdk 26 ...Windows users (native, not WSL): Download the JDK 26 from Adoptium and add it to your
PATH. JBang works natively on Windows — no WSL required.
JBang lets you run .java files as if they were shell scripts — no pom.xml, no build.gradle, no compile step.
Linux / macOS / WSL:
curl -Ls https://sh.jbang.dev | bash -s - app setupWindows (PowerShell):
iex "& { $(iwr https://ps.jbang.dev) } app setup"Via SDKMAN (recommended if you already have it):
sdk install jbangVerify:
jbang --version
# Expected: JBang v0.x.x- Go to Google AI Studio
- Sign in with a Google account
- Click "Create API Key"
- Copy the key
Set the environment variable:
# Linux / macOS / WSL — add to ~/.bashrc or ~/.zshrc for persistence:
export GEMINI_API_KEY="your-api-key-here"
# Windows (PowerShell) — permanent for current user:
[System.Environment]::SetEnvironmentVariable("GEMINI_API_KEY", "your-api-key-here", "User")
# Windows (Command Prompt) — current session only:
set GEMINI_API_KEY=your-api-key-hereSecurity note: Never commit your API key to version control. JForge reads it exclusively from the environment variable — it is never written to disk or logged.
# Clone the repository
git clone https://github.com/gazolla/JForgeAgent.git
cd JForgeAgent
# Run on Linux / macOS
jbang JForgeAgent.java
# Run on Windows (PowerShell or CMD)
jbang .\JForgeAgent.javaOn first run, JBang will download all dependencies automatically (Google ADK, picocli, Gson, slf4j). This takes ~30 seconds once — subsequent runs are instant.
Expected welcome screen:
[LLM] Model: gemini-2.5-pro-preview | Agents: supervisor, router, coder, assistant, searcher, tester
Welcome to JForge V1.0 - Tool Orchestrator.
Available tools are cached in: /path/to/JForgeAgent/tools
Logs are recorded in: /path/to/JForgeAgent/logs
Workspace [Products]: /path/to/JForgeAgent/products
Workspace [Artifacts]: /path/to/JForgeAgent/artifacts
🤖 What would you like to achieve? (or 'exit'/'quit'):
Every user prompt passes through two layers before any work is done:
Layer 1 — SUPERVISOR: What to do, in what order, with what dependencies?
Layer 2 — ROUTER: How to achieve each step? (search, create, execute, chat)
This separation means the Supervisor never needs to know about APIs, tools, or code — it only plans the workflow. The Router never needs to know about the big picture — it handles one sub-goal at a time with full intelligence.
The Supervisor classifies every request as SIMPLE or WORKFLOW before doing anything:
- SIMPLE — conversational questions, single known-tool calls, or follow-ups reusing a cached tool. The Supervisor returns
{"type":"SIMPLE"}and the request goes directly to the Router — no plan, no file I/O, one fewer LLM call. - WORKFLOW — requests that need a new tool created, result chaining between steps, parallel execution, or file generation. The Supervisor returns a full
WorkflowPlanand the WorkflowExecutor runs it.
The Supervisor receives your goal and the list of currently cached tools, then returns one of two JSON responses:
SIMPLE bypass — no plan needed:
{"type": "SIMPLE"}WORKFLOW plan — full decomposition with dependencies:
{
"type": "WORKFLOW",
"goal": "Get weather for Southeast Brazilian cities and create a PDF summary",
"steps": [
{ "id": "s1", "goal": "Get weather for Rio de Janeiro", "dependsOn": [] },
{ "id": "s2", "goal": "Get weather for São Paulo", "dependsOn": [] },
{ "id": "s3", "goal": "Get weather for Belo Horizonte", "dependsOn": [] },
{ "id": "s4", "goal": "Create a PDF summary using: <<s1>> | <<s2>> | <<s3>>",
"dependsOn": ["s1", "s2", "s3"] }
]
}What the Supervisor decides:
- How many steps are needed (one for simple tasks, many for complex workflows)
- Which steps depend on which others (
dependsOn) - What each step should achieve (
goal) — using<<stepId>>to inject the output of a previous step
What the Supervisor does NOT decide:
- Whether to SEARCH, CREATE, EXECUTE, or DELEGATE_CHAT — that is the Router's job
- Which tool to use — the Router decides from the cache
WORKFLOW plans are saved to workflows/workflow_<timestamp>.json for auditing and pattern reuse. SIMPLE requests are not saved — they leave no file on disk. If a step fails and the Supervisor is asked to replan, each revised version is saved to logs/ as workflow_<timestamp>_replan1.json, etc.
Fallback: If the Supervisor fails to produce a valid plan (LLM error or malformed JSON), JForge falls back automatically to the direct Router loop — no interruption for the user.
The WorkflowExecutor receives the plan and runs it:
- Topological sort — groups steps into layers based on
dependsOn. Steps with no mutual dependency share the same layer. - Layer-by-layer execution — each layer completes before the next starts.
- Parallel execution within a layer — steps in the same layer run as VirtualThreads (Java 26), so independent tasks execute concurrently.
- Result chaining — after each step, its output is stored and substituted into any
<<stepId>>placeholder in later steps' goals. - Replanning on failure — if a step produces no output, the Supervisor is asked to replan (max 2 replans).
Example execution trace for the weather PDF request:
[SUPERVISOR] 4 steps planned for: Get weather for Southeast Brazilian cities and create a PDF
[EXECUTOR] Layer 1/2: [s1, s2, s3] ← 3 steps run concurrently
[STEP s1] → Get weather for Rio de Janeiro → EXECUTE: CityWeather.java "Rio de Janeiro"
[STEP s2] → Get weather for Sao Paulo → EXECUTE: CityWeather.java "Sao Paulo"
[STEP s3] → Get weather for Belo Horizonte → EXECUTE: CityWeather.java "Belo Horizonte"
[EXECUTOR] Layer 2/2: [s4]
[STEP s4] → Create a PDF using: rio: +26°C | sao: +5°C | belo: +22°C
→ EDIT: WeatherPdfGenerator.java (Router edits with real data)
→ EXECUTE: WeatherPdfGenerator.java
[GUARDRAIL] Output file moved to products/: WeatherSummary.pdf
LoopState fields (per Router sub-loop, one per step):
| Field | Purpose |
|---|---|
taskResolved |
Signals the Router loop to exit cleanly |
lastError |
Holds the stack trace from a failed tool execution |
crashRetries |
Counts auto-heal attempts (max 2 before aborting) |
searchCount |
Limits web searches per step (max 3) |
ragContext |
Accumulates web search results as live knowledge |
cacheList |
Snapshot of tools on disk (lazy-loaded, invalidated after writes) |
stepResults |
Accumulated outputs keyed by step id — used for <<stepId>> chaining |
Each agent is a stateless InMemoryRunner wrapping a LlmAgent backed by Google Gemini. They share no memory — context is injected as text on every call.
Receives the user goal and the cached tool list. Classifies the request as SIMPLE or WORKFLOW:
- SIMPLE → returns
{"type":"SIMPLE"}and the Router is invoked directly. No plan is created, no file is saved — zero overhead for trivial requests. - WORKFLOW → returns a full
WorkflowPlanJSON defining sub-goals, dependencies, and parallel layers. Used when a new tool must be created, results need to be chained between steps, or a final file (PDF, CSV) must be generated.
Does not decide implementation details — that is the Router's domain.
The Router reads everything: the sub-goal from the Supervisor, the tool cache, the system clock, the RAG context, and any prior error. It returns exactly one of these five commands:
| Command | Meaning |
|---|---|
EXECUTE: ToolName.java [args...] |
Run an existing cached tool |
CREATE: <instruction> |
Forge a new tool from scratch |
EDIT: ToolName.java <changes> |
Modify an existing tool |
SEARCH: <query> |
Fetch live data using Google Search |
DELEGATE_CHAT |
Answer conversationally (no tool needed) |
The Router is aware of time (it knows today's date and time zone), so it uses SEARCH before answering factual questions like prices, news, or current events — never hallucinating.
The Coder receives a precise instruction (or the existing code + change request) and returns a structured response containing two mandatory sections:
//METADATA_START
{
"name": "WeatherTool.java",
"description": "Fetches current weather from wttr.in for a given city",
"args": ["city name"]
}
//METADATA_END
//FILE: WeatherTool.java
//DEPS com.squareup.okhttp3:okhttp:4.12.0
... java code ...
The metadata is saved as WeatherTool.meta.json alongside the script. This JSON is what the Router reads when deciding whether an existing tool can fulfill a future request — making the cache semantically searchable, not just by filename.
The Coder is explicitly instructed to never swallow exceptions — tools must crash loudly so the orchestrator can detect failure and trigger auto-heal.
The Searcher is equipped with the GoogleSearchTool. It performs the actual web queries when requested by the Router. It returns a structured plain-text report focusing on:
- Technical API endpoints and documentation.
- Factual data points and dates.
- Direct sources and URLs for grounding.
This agent ensures that the system doesn't rely on hallucinations but on real-time indexed data.
When the Router decides no code is needed, the Assistant takes over. It receives your original prompt, the current system clock, the list of available cached tools, and any RAG context gathered by prior SEARCH commands. It responds in clean Markdown, grounded in real data.
After every successful CREATE, the Tester generates a single safe test invocation and runs it immediately. No user interaction required.
- Pass → pipeline continues normally to the final EXECUTE with the user's real arguments.
- Fail → the error output becomes
state.lastError; the Router issuesEDITon the next iteration to auto-heal before the tool ever reaches the user.
Use --skip-test to bypass this step for tools that open GUI windows or require physical hardware.
Before any generated code is written to disk, JForge runs four fast structural checks:
| Check | What it catches |
|---|---|
| Blank body | Coder returned an empty code block |
Missing //DEPS |
JBang dependency directive absent |
Missing class / void main |
Incomplete Java structure |
| Leaked markdown fences | LLM ignored the no-markdown rule |
If any check fails, the file is never written. state.lastError is set with a clear message and the Router retries with EDIT on the next iteration — without polluting the tools/ directory with broken files.
JForge creates and manages five directories relative to where you run it:
./
├── tools/ ← Generated .java scripts + .meta.json schemas
│ ├── WeatherFetcher.java
│ ├── WeatherFetcher.meta.json
│ └── ...
├── workflows/ ← Successful WorkflowPlan JSONs (read by Supervisor for pattern reuse)
│ └── workflow_20260411_164500.json
├── logs/ ← Session logs + failed replan JSONs (audit only)
│ ├── session_20260411_164500.log
│ └── workflow_20260411_164500_replan1.json
├── memory/ ← Persistent conversation memory (one entry per line)
│ └── context.json
├── artifacts/ ← Temporary data written by tools (extractions, raw downloads)
└── products/ ← Final output files for the user (reports, PDFs, exports)
Tools are instructed to use the absolute paths of artifacts/ and products/ — not relative paths — ensuring they work correctly regardless of the directory a tool's process is spawned from.
JForge has two active guardrails that correct common problems automatically — no user intervention required.
If the Router mis-routes to DELEGATE_CHAT but the response mentions the name of a cached tool, JForge detects the mis-route and forces re-routing to EXECUTE:
[GUARDRAIL] DELEGATE_CHAT mentioned 'WeatherTool.java' — overriding to EXECUTE
Tools run with tools/ as their working directory. If a generated tool saves an output file (PDF, CSV, image, etc.) using a relative path or a malformed absolute path, it lands inside tools/ instead of products/. After every tool execution, JForge scans tools/ and moves any stray output files to products/ automatically:
[GUARDRAIL] Output file moved to products/: WeatherSummary.pdf
The guardrail also removes stray subdirectories left by path-construction bugs (e.g. directories whose names contain literal quote characters). It correctly ignores:
.javaand.meta.jsonfiles (legitimate tool files)- Hidden files:
.DS_Store,.gitignore, etc. (macOS/Linux) - Windows system files:
Thumbs.db,desktop.ini, etc.
JForge executes LLM-generated code on your machine. Several layers of defense are active:
| Layer | Mechanism |
|---|---|
| Name validation | Tool names must match [A-Za-z0-9_\-]+\.java — no path separators, no extension tricks |
| Path containment | Resolved tool path must remain inside tools/ after normalization |
| Symlink rejection | Symbolic links inside tools/ are rejected at execution time |
| Arg sanitization | LLM-supplied arguments are filtered against a blocklist: -D, -X, --classpath, --deps, --jvm-options, --, -agent, --source |
| Process timeout | Tools are killed after 120 seconds (MAX_TOOL_TIMEOUT_SECONDS) |
| LLM timeout | Agent API calls time out after 60 seconds via CompletableFuture.orTimeout() |
| Loop guard | Each Router sub-loop aborts after 10 iterations regardless of state |
| Replan limit | The Supervisor may replan at most 2 times per user request |
| Search guard | Maximum 3 web searches per Router sub-loop |
| Crash limit | Auto-heal retries are capped at 2 attempts per step |
JForge automatically saves conversation history to memory/context.json between sessions. On the next startup, the Supervisor and Router read the last interactions as [Recent Chat History], allowing the system to:
- Recognize tools it built in a previous session without rebuilding them
- Avoid repeating web searches already performed
- Maintain conversational continuity across restarts
The memory file stores entries in plain text (one per line) and is updated after every interaction. It is capped at 20 entries — the oldest are evicted when full. To reset memory to a clean state, simply delete memory/context.json.
JForge manages its own tool cache autonomously — no manual cleanup needed.
The GC runs at most once per minute (throttled to avoid redundant filesystem scans) and applies two eviction policies in order:
- Age-based eviction: Tools not accessed for more than
--tool-age-days(default: 30 days) are deleted along with their.meta.jsoncompanion. - Count-based eviction: If more than
--max-tools(default: 10) tools remain after age eviction, the least recently used ones are removed until the count is within the limit.
This keeps the cache small and semantically fresh — the Router always sees tools that are relevant to recent usage patterns.
jbang JForgeAgent.java [OPTIONS]| Option | Default | Description |
|---|---|---|
--supervisor-model |
gemini-2.5-flash |
Model for the Supervisor agent |
--router-model |
gemini-2.5-flash |
Model for the Router agent |
--coder-model |
gemini-2.5-flash |
Model for the Coder agent |
--assistant-model |
gemini-2.5-flash |
Model for the Assistant agent |
--searcher-model |
gemini-2.5-flash |
Model for the Searcher agent |
--tester-model |
gemini-2.5-flash |
Model for the Tester agent |
--max-tools <n> |
10 |
Maximum cached tools before GC count-eviction |
--tool-age-days <n> |
30 |
Days of inactivity before a tool is eligible for deletion |
--prompt <text> |
— | Run a single prompt non-interactively and exit (CI/CD mode) |
--silent |
false |
Suppress all status/decorative output; print only the final result (for pipe/MCP/A2A use) |
--skip-test |
false |
Skip the auto-test after CREATE (use for GUI/Swing or hardware-dependent tools) |
-V, --version |
— | Print version and exit |
-h, --help |
— | Print help and exit |
By default, all agents use gemini-2.5-flash (free tier compatible). Users with billing enabled can upgrade the reasoning agents to gemini-2.5-pro:
jbang JForgeAgent.java --supervisor-model gemini-2.5-pro --router-model gemini-2.5-pro --coder-model gemini-2.5-proExamples:
# Default run — per-agent model assignment is automatic
jbang JForgeAgent.java
# Override only the Coder to a newer model, keep others as default
jbang JForgeAgent.java --coder-model gemini-2.5-pro
# Aggressive GC: delete tools unused for more than 7 days, keep max 5
jbang JForgeAgent.java --tool-age-days 7 --max-tools 5
# Run a single prompt without opening an interactive session
jbang JForgeAgent.java --prompt "What is the current price of Bitcoin?"
# Machine-readable output for piping
jbang JForgeAgent.java --silent --prompt "Fetch BTCUSDT price as JSON" | jq '.price'JForge can run a single prompt without an interactive terminal — ideal for shell scripts, CI/CD pipelines, and automation:
# Ask a question non-interactively
jbang JForgeAgent.java --prompt "What is the current price of Bitcoin?"
# Multi-step workflow non-interactively
jbang JForgeAgent.java --prompt "Get weather for London, Paris and Berlin and summarize"
# Use inside a shell script
jbang JForgeAgent.java --prompt "Fetch the ISS location and save the map to products/"When --prompt is provided, JForge:
- Initializes all six agents normally
- Loads persistent memory from previous sessions
- Runs the Supervisor — returns
SIMPLE(routes directly to Router) or aWorkflowPlan(runs through WorkflowExecutor) - Executes each step through the Router loop
- Prints the result and exits with code
0
Add --silent to suppress all status messages, agent names, and decorative output. Only the final result is printed to stdout — no ANSI codes, no brackets, no banners:
# Output only the final result — pipe-friendly
jbang JForgeAgent.java --silent --prompt "What is the current Bitcoin price?"
# Capture the result in a shell variable
RESULT=$(jbang JForgeAgent.java --silent --prompt "Summarize the latest Java 26 features")
echo "$RESULT"Once running, just type your request at the prompt. The Supervisor handles planning, the Router handles execution.
For conversational or single-step tasks, the Supervisor returns {"type":"SIMPLE"} and the Router is invoked directly — no plan, no file write.
What is the current price of Bitcoin in USD?
Supervisor → SIMPLE bypass → Router →
SEARCH→DELEGATE_CHATwith RAG context
Who won the last FIFA World Cup and what was the final score?
Supervisor → SIMPLE bypass → Router →
SEARCH→DELEGATE_CHAT
Create a tool that fetches the current weather for any city using wttr.in
Supervisor → 1 step → Router →
CREATE: WeatherTool.java→EXECUTE: WeatherTool.java "London"
Build a currency converter that uses the Frankfurter API to convert amounts between any two currencies
Supervisor → 1 step → Router →
SEARCH→CREATE: CurrencyConverter.java→EXECUTE
Create a tool using the ZXing library that generates a QR code PNG from any text I give it and saves it to the products folder
Supervisor → 1 step → Router →
CREATE: QRCodeGen.java→EXECUTE→ Guardrail movesqrcode.pngtoproducts/
These requests trigger the Supervisor to generate a multi-step plan.
Parallel data collection + summary:
Get the current weather for London, Tokyo and New York and write a comparison summary
[SUPERVISOR] 4 steps planned
[EXECUTOR] Layer 1/2: [s1, s2, s3] ← 3 cities fetched in parallel
[STEP s1] → weather London → EXECUTE: WeatherTool.java "London"
[STEP s2] → weather Tokyo → EXECUTE: WeatherTool.java "Tokyo"
[STEP s3] → weather New York → EXECUTE: WeatherTool.java "New York"
[EXECUTOR] Layer 2/2: [s4]
[STEP s4] → summarize <<s1>> | <<s2>> | <<s3>> → DELEGATE_CHAT
Create once, run in parallel:
Pesquise o clima de Rio de Janeiro, São Paulo e Belo Horizonte e crie um PDF com o resumo
[SUPERVISOR] 4 steps planned
[EXECUTOR] Layer 1/2: [s1, s2, s3] ← all 3 fetched in parallel
[EXECUTOR] Layer 2/2: [s4]
[STEP s4] → Create PDF with: rio +26°C | sao +5°C | belo +22°C
→ EDIT: WeatherPdfGenerator.java (updates data)
→ EXECUTE: WeatherPdfGenerator.java
[GUARDRAIL] Output file moved to products/: WeatherSummary.pdf ✓
Search → create → run chain:
Find a free DNS benchmark API and build a tool that measures latency for the top 5 public DNS servers
[SUPERVISOR] 3 steps planned
[EXECUTOR] Layer 1/3: [s1] → SEARCH: free DNS benchmark / public DNS server IPs
[EXECUTOR] Layer 2/3: [s2] → CREATE: DnsBenchmark.java using <<s1>>
[EXECUTOR] Layer 3/3: [s3] → EXECUTE: DnsBenchmark.java
Update the weather tool to also show wind speed and humidity, not just temperature
Supervisor → 1 step → Router →
EDIT: WeatherTool.java "Add wind speed and humidity"
The currency converter only does one conversion. Make it accept a list of target currencies and show all at once
Supervisor → 1 step → Router →
EDIT: CurrencyConverter.java "Accept multiple target currencies"
After a tool is built, the Supervisor sees it in the cache. For a single-city follow-up it returns SIMPLE, routing directly to the Router which reads the cache and executes.
# After building WeatherTool.java:
What's the weather in Tokyo?
Supervisor → SIMPLE bypass → Router reads cache →
EXECUTE: WeatherTool.java "Tokyo"
# Multi-city request with existing tool:
Show me the weather for all G7 capitals
Supervisor → 7 parallel EXECUTE steps (one per capital) + 1 summary step
Build a Swing app that shows a live analog clock with the current local time, updating every second
Supervisor → 1 step → Router →
CREATE: AnalogClock.java→EXECUTE— opens a desktop window
Create a bar chart using the XChart library that plots the monthly average temperature for São Paulo
Supervisor → 1 step → Router →
CREATE: TemperatureChart.java→ opens chart window
Use --skip-test for GUI/Swing tools since the auto-test can't validate windowed output.
If a generated tool crashes, the Router repairs it automatically — within the same step:
[CODER] Developing new Tool -> GitHubTrending.java
[EXECUTE] EXECUTE: GitHubTrending.java
Exception in thread "main" java.io.IOException: Server returned HTTP response code: 403
Tool Execution Failed. Returning trace to Router for analysis...
[CODER] Modifying existing tool -> GitHubTrending.java
Fix: Add proper User-Agent header to bypass GitHub anonymous request blocking
[EXECUTE] EXECUTE: GitHubTrending.java
1. awesome-llm-apps 42.3k ✓
SIMPLE requests leave no file on disk. WORKFLOW plans are saved to workflows/ on success (and read back by the Supervisor for pattern reuse):
cat workflows/workflow_20260410_143022.json{
"type": "WORKFLOW",
"goal": "Get weather for Southeast Brazilian cities and create PDF",
"steps": [
{"id":"s1","goal":"Get weather for Rio de Janeiro","dependsOn":[]},
{"id":"s2","goal":"Get weather for Sao Paulo","dependsOn":[]},
{"id":"s3","goal":"Get weather for Belo Horizonte","dependsOn":[]},
{"id":"s4","goal":"Create PDF summary using: <<s1>> | <<s2>> | <<s3>>","dependsOn":["s1","s2","s3"]}
]
}If replanning occurred, each revised plan is saved to logs/ with a _replan1, _replan2 suffix alongside the session log.
| Symptom | Likely Cause | Fix |
|---|---|---|
Please set the GEMINI_API_KEY |
API key not set | Export GEMINI_API_KEY in your shell profile and restart the terminal |
Interactive console is not supported |
Running inside an IDE terminal or piped input | Run in a real terminal (Windows Terminal, iTerm2, Bash) |
[LLM ERROR] Context variable not found |
Template variable in agent instruction | Fixed in current version — update to latest |
[SUPERVISOR] Simple request — bypassing plan, routing directly |
Supervisor classified the request as SIMPLE (no plan needed) | Normal — one fewer LLM call; the Router handles it directly |
[SUPERVISOR] No valid plan produced — falling back to Router mode |
Supervisor LLM error or malformed JSON response | Automatic fallback; if persistent, try rephrasing the prompt |
[TIMEOUT] Tool exceeded 120s |
Tool has an infinite loop or blocking network call | Ask JForge: "Edit the tool to add a 10-second HTTP timeout" |
[LLM ERROR] LLM API call failed |
Network issue or Gemini quota exceeded | Check your internet and quota at Google AI Studio |
[LOOP GUARD] Maximum iterations reached |
Router is stuck in a SEARCH/CREATE cycle | Rephrase your prompt with more specific instructions |
[STEP sN] produced no output |
LLM timeout or loop-guard abort on a workflow step | Supervisor will automatically replan (max 2 attempts) |
[GUARDRAIL] Output file moved to products/: X |
Tool saved output to tools/ instead of products/ |
Normal — guardrail handled it; file is in products/ |
[TEST FAILED] Tool failed validation |
Newly created tool crashed during auto-test | Auto-heal activates automatically; use --skip-test for GUI or hardware tools |
[VALIDATION] Validation failed |
LLM generated malformed code | JForge retries automatically; if it persists, try rephrasing |
[GUARDRAIL] DELEGATE_CHAT mentioned 'X.java' — overriding to EXECUTE |
Router mis-routed when a cached tool should have run | Automatic — guardrail forces re-routing to EXECUTE |
| Tool compiles but produces wrong output | LLM logic error | Ask: "The result was wrong. Fix [ToolName.java] to correctly handle [case]" |
| Requirement | Version | Notes |
|---|---|---|
| Java | 26+ | Install via SDKMAN: sdk install java 26-tem |
| JBang | Latest | Install via SDKMAN: sdk install jbang |
| Gemini API Key | — | Free tier available at aistudio.google.com |
| Internet access | — | Required for Gemini API and web search |
| Disk space | ~50 MB | JBang dependency cache on first run |
JBang Dependencies (auto-resolved):
| Dependency | Version | Purpose |
|---|---|---|
com.google.adk:google-adk |
1.0.0-rc.1 |
LLM agents + Google Search tool |
info.picocli:picocli |
4.7.5 |
CLI parsing and ANSI output |
com.google.code.gson:gson |
2.10.1 |
WorkflowPlan JSON parsing |
org.slf4j:slf4j-simple |
2.0.12 |
Logging backend |
JForge V1.0 — Built with Google ADK, JBang, picocli and Gson.