An agentic terminal REPL and single-process engine built for governed AI deep research and software development. nehanda-cli connects directly to our flagship Nehanda v3, as well as local or cloud-based Ollama models, LM Studio instances, or any OpenAI-compatible API.
Every conversation turn, tool call, phase transition, and permission check is stored in a local, queryable SQLite database you own, ensuring complete transcripts exist for auditing and debugging.
- Node.js: v22.0.0 or higher
- SQLite: Local SQLite runtime support
git clone [https://github.com/AsobaCloud/nehanda-cli.git](https://github.com/AsobaCloud/nehanda-cli.git)
cd nehanda-cli
npm install
Launch the interactive Ink TUI:
npm start
# or directly run:
node bin/nehanda-ui.mjs
The CLI defaults to the primary Nehanda endpoint (https://nehanda-ml.asoba.co/v1). If an API key is required:
❯ /key
New Nehanda API key: <your-key>
Start LM Studio locally on port 1234 or 8000, then start the CLI. Select or switch models via:
❯ /model
To connect to an Ollama instance running on your network:
❯ /config base_url [http://AsobaCorp-1.local:11434/v1](http://AsobaCorp-1.local:11434/v1)
❯ /model ollama/deepseek-coder-v2:latest
-
In-Process Engine: Executes turns directly inside the process via
runUserTurn, eliminating separate server daemons or background HTTP relays. -
Dynamic Tool Rescue (
[TOOL_CALL]): Native support for endpoints that strip OpenAI tool schemas (such asnehandaMlProxy). The engine dynamically injects active tool schemas directly into system prompts as[TOOL_CALL]blocks, parsing and executing tools locally without server-side function-calling support. Uses[TOOL_CALL]delimiters instead of<tool_call>XML to prevent vLLM's--tool-call-parser qwen3_xmlstop-token interception. -
Deterministic SDLC Workflow: Enforces a 6-phase state machine (
idle→plan→implement→test→verify→done) to prevent unapproved code changes, hallucinated test passes, or unverified implementations. -
Interactive TUI & Pipe Support: Rich Ink-based TUI (
bin/nehanda-ui.mjs) for interactive development sessions, with headless pipe-mode support (bin/agent.mjs) for acceptance testing and automation. -
Multi-Provider Switching: Seamlessly switch between Nehanda 27B, local LM Studio, Ollama instances over LAN, Anthropic Claude, or any OpenAI-compatible API using
/model.
nehanda-cli combines an Ink TUI with a deterministic orchestration engine:
The Nehanda vLLM deployment runs with --tool-call-parser qwen3_xml and --enable-auto-tool-choice flags. The nehandaMlProxy Lambda function strips tools and tool_choice from requests before forwarding to vLLM to avoid a Qwen3 chat template bug where the presence of a tools array causes system message ordering errors.
To enable tool calling despite this constraint, the engine uses a rescue path:
- System Prompt Injection:
buildXmlToolInstructions()injects tool schemas into the system prompt using[TOOL_CALL]...[/TOOL_CALL]delimiters. - Late Directive Injection: A
[SYSTEM DIRECTIVE]is appended to the final user message to defeat token recency bias on reasoning models[cite: 5, 7]. - Model Generation: The model emits tool calls in the
[TOOL_CALL]format within its response text. - Local Parsing & Execution:
parseXmlToolCalls()extracts and executes these calls locally viaexecuteBuiltinTool(), continuing the execution loop even when backends returnfinish_reason: "stop"[cite: 5, 7].
The vLLM --tool-call-parser qwen3_xml flag registers <tool_call> XML tags as stop/intercept tokens. When the model emits them in plain text, vLLM terminates generation mid-sentence and hands off to its native parser—which returns nothing because the plain-text path never populates message.tool_calls. This results in truncated responses containing only thinking traces.
Switching to [TOOL_CALL]...[/TOOL_CALL] delimiters bypasses vLLM's stop-token interception entirely, allowing the model to complete generation and return valid, parseable tool calls.
nehanda-cli supports a zero-code tool configuration pattern. Tools are registered by dropping a JSON config into lib/tools/ and a corresponding script into lib/scripts/. No JavaScript changes are required.
- On startup,
lib/tools.mjsscanslib/tools/*.jsonand dynamically registers each tool. - Tool schemas are injected into the API
toolsarray so the model can call them. - Phase visibility (
explore_only,planning_blocked) and mandatory enforcement (mandatory_in) are driven by config metadata. - Prompt injection (
prompt.mandatory_instruction,prompt.available_hint) is read from the config and injected into the appropriate SDLC phase system prompts automatically.
| Tool | Script | What It Checks |
|---|---|---|
AuditCodeIntegrity |
lib/scripts/audit-code-integrity.py |
Lifecycle teardown parity, mock-theater tests, naming invariants, swallowed exceptions |
ShellSafetyChecker |
lib/scripts/shell-safety-checker.sh |
Missing set -euo pipefail, background job silent failure risk, hardcoded credentials |
JsSafetyChecker |
lib/scripts/js-safety-checker.cjs |
Duplicate functions, duplicate HTML element IDs, script block syntax errors |
PythonSafetyChecker |
lib/scripts/python-safety-checker.py |
Bandit security issues, ruff lint, mutable default args, eval/exec/pickle usage |
All four are mandatory in the test phase and available on-demand in explore and idle phases.
Create a JSON config in lib/tools/:
{
"name": "MyNewTool",
"description": "What the tool does.",
"input_schema": {
"type": "object",
"properties": {
"target": { "type": "string", "description": "Target path" }
}
},
"phases": {
"explore_only": true,
"planning_blocked": false,
"mandatory_in": ["test"]
},
"prompt": {
"mandatory_instruction": "Run MyNewTool on the workspace to check for X.",
"available_hint": "Checks for X, Y, and Z"
},
"execution": {
"runtime": "python3",
"script": "lib/scripts/my-new-tool.py",
"args": ["{{target}}"],
"default_timeout": 120000,
"max_timeout": 600000
}
}Place the script at lib/scripts/my-new-tool.py. It will receive arguments interpolated from args and run with cwd set to the target workspace. No JS code changes needed.
| Command | Description |
|---|---|
/help |
Display available commands |
/model [name] |
Discover and switch active provider or model endpoint |
/key |
Save Nehanda API key |
/config |
View or set settings (e.g., /config base_url <url>) |
/mcp |
Manage MCP server connections (see below) |
/clear |
Clear conversation history and reset transcript state |
/retry |
Resend the last failed request |
/exit |
Exit the REPL |
| Sub-command | Description |
|---|---|
/mcp status |
List all configured MCP servers and their commands |
/mcp list |
Connect to each server and enumerate available tools |
/mcp reload [server] |
Re-read mcp.json and bust the tool cache (optionally for one server) |
/mcp add <name> <command> [args…] |
Register a new server and save it to ~/.config/nehanda/mcp.json |
/mcp env |
Show environment variables for all configured servers |
/mcp env <server> |
Show environment variables for a specific server |
/mcp env <server> <KEY> <value> |
Set an environment variable (e.g. an API key) |
/mcp env <server> <KEY> |
Clear an environment variable |
nehanda-cli can consume tools from any external MCP server. Servers are configured in a standard mcp.json file using the same schema as Claude Desktop and other MCP clients.
Config file locations (both are read and merged at startup; project-local takes priority):
./mcp.json— project-local, committed with the repo~/.config/nehanda/mcp.json— user global
Example mcp.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
},
"powermcp": {
"command": "npx",
"args": ["-y", "harvard-powermcp"],
"env": { "API_KEY": "your-key" }
}
}
}At startup, nehanda-cli loads mcp.json, spawns the configured servers, and calls tools/list on each. Discovered tools are injected into the model's tool list under the namespace mcp__<server>__<tool> and are available for the model to call during any conversation turn — no additional configuration required.
Use /mcp list to verify what tools are visible, and /mcp reload to pick up changes without restarting.
Many MCP servers require an API key or other secrets. Rather than editing mcp.json by hand, use the /mcp env command directly from the REPL:
❯ /mcp env # show env vars for all servers
❯ /mcp env asoba # show env vars for 'asoba' server
❯ /mcp env asoba ASOBA_API_KEY sk-abc123... # set the key
✓ Set ASOBA_API_KEY = ******** on asoba
Config file: ~/.config/nehanda/mcp.json
This writes the value to the mcp.json file where the server is defined (project-local ./mcp.json takes priority over global ~/.config/nehanda/mcp.json), then automatically reloads the server so the change takes effect immediately.
To clear a key:
❯ /mcp env asoba ASOBA_API_KEY
✓ Cleared ASOBA_API_KEY on asoba
Tip: Keys are masked in /mcp env output. Only the first 6 and last 4 characters are shown.
Use /config set <dot.path> <value> to set any configuration value without editing files:
❯ /config set model_config.base_url https://api.anthropic.com
❯ /config set model_config.num_ctx 8192
Numeric values are auto-converted. Changes persist in the local settings database.
The engine enforces state transitions across six distinct phases:
-
idle: Discovery and triage. Mutating file tools are physically masked out. Safety and analysis tools are available on-demand. -
plan: Model formulates success criteria and implementation steps (EnterPlanMode). No tools available. -
implement: Code changes applied using file editing and shell execution (ExitPlanMode). -
test: Automated test generation and execution (SubmitImplementation). After tests pass, all tools markedmandatory_in: ["test"]must be run. The system prompt enforces this — the model cannot declare success until all mandatory safety checkers pass. -
verify: Inspection of test outputs and coverage verification (SubmitTest). -
done: Final sign-off and git commit creation.
Session state is persisted locally at ~/.config/nehanda/ona-session.db. Key tables include:
-
conversations: Active workflow phases and project roots. -
transcript_entries: Sequence of user messages, assistant turns, tool calls, and results. -
plans: Content, hashes, and approval status for technical plans. -
events: SDLC milestones and test execution output.
Run the acceptance suite:
npm run acceptance
Verify SDLC hook ordering:
npm run verify
See LICENSE for details.