Standalone Neovim-native agentic coding assistant built from scratch in Lua.
neocoder currently includes:
- Command and keymap entrypoints
- Turn orchestration (
ask,search,visual_edit) - Typed plan schema + planner fallback strategy
- Deterministic step execution
- Policy + approval gates (read-only-first default)
- Tool runtime (
read_file,write_file,apply_patch,grep,glob,ls,bash,git,diagnostics) - Scoped child-task tool (
task) with step/tool limits - Bounded subtask scheduler with depth and fanout limits
- Provider adapters (
opencode,codex,claude,azure_openai) - Project instruction file injection (
neocoder.md/avante.md/.neocoder/instructions.md) - Prompt directive DSL (slash commands, mentions, shortcuts)
- Adaptive scope inference (
selection|file|module|repo|external) with prompt tags - Autonomous route planning with bounded subtasks
- Retrieval pipeline with staged evidence collection, ranking, and dedupe
- Repo map symbol extraction stage for retrieval
- Optional RAG sidecar integration (
rag_searchtool + status surfaces) - Local daemon sidecar (
scripts/neocoderd.py) for scope/index/verification acceleration - Verification gate for code-intent turns (
verification.require_pass_for_success) - Dispatch-agent read-only sub-agent tool (
dispatch_agent) - MCP stdio JSON-RPC runtime + dynamic registry + project lock + local/external surf hooks
- Floating UI + optional workspace layout (
ui.layout = "workspace") - Persistence in
.neocoder/(events, plan, state, patch artifacts) - Typed runtime events (
step_start,tool_call,tool_result,tool_error, ...)
Example with lazy.nvim:
{
dir = "/Users/divyansh/experiments/neocoder",
config = function()
require("neocoder").setup({
provider = {
adapter = "opencode", -- opencode | codex | claude | azure_openai
fallback_adapters = { "codex", "claude" },
model = "opencode/claude-sonnet-4-5",
},
safety = {
mode = "read_only_first",
autopilot_mode = "guarded", -- guarded | strict | aggressive
dangerous_shell = "deny", -- deny | ask
allow_safe_readonly_bash = true,
},
planner = {
review_before_execute = false, -- set true to require plan approval
},
})
end,
}Approval scopes:
Once: allow this exact action onceAlways (Session): remember as wildcard rule for current sessionAlways (Project): persist wildcard rule to.neocoder/policy.json
Azure OpenAI example:
require("neocoder").setup({
provider = {
adapter = "azure_openai",
azure_openai = {
endpoint = "https://YOUR_RESOURCE.openai.azure.com",
deployment = "gpt-4.1",
api_version = "2024-10-21",
api_mode = "chat_completions", -- or "responses"
api_key_env = "AZURE_OPENAI_API_KEY",
},
},
})Provider fallback behavior:
- NeoCoder tries
provider.adapterfirst. - If it fails, it tries
provider.fallback_adaptersin order. - You can also provide per-request fallback via
fallback_adapters.
Session runtime override behavior:
:NeocoderSwitchProvidersets a session-level provider adapter.:NeocoderModelsets a session-level model override.:NeocoderProviderdisplays configured vs active runtime adapter/model.
NeoCoder can run a local daemon (scripts/neocoderd.py) to speed up scope decisions, indexed retrieval, and verification.
Requirements:
python3available in PATH (or setdaemon.python_cmd)
Example config:
require("neocoder").setup({
daemon = {
enabled = true,
auto_start = true, -- auto-start on first daemon request
start_on_setup = false, -- optional eager start in setup()
host = "127.0.0.1",
port = 45454,
python_cmd = "python3",
use_for_scope = true,
auto_index = true,
use_for_verification = true,
},
verification = {
enabled = true,
require_pass_for_success = true,
fallback_to_local_on_daemon_error = true,
auto_detect = true, -- package.json/Makefile detection when commands unset
timeout_ms = 120000,
commands = {
build = nil,
test = nil,
lint = nil,
},
},
})Runtime visibility:
:NeocoderStatusincludes daemon state and last health/error details.:NeocoderDoctorreports daemon script resolution/readability and verification config.:NeocoderDaemonshows daemon process info and recent daemon logs.:NeocoderDaemonStart,:NeocoderDaemonStop,:NeocoderDaemonReindexmanage daemon lifecycle manually.:NeocoderVerifyruns verification on-demand outside a coding turn.- If daemon verification fails and
verification.fallback_to_local_on_daemon_error = true, NeoCoder runs local verification as a fallback.
:NeocoderAsk [prompt]:NeocoderSearch [prompt]:NeocoderVisual [prompt]:NeocoderHistory:NeocoderToggle:NeocoderChatNew:NeocoderClear:NeocoderCompact:NeocoderResume <session_id>:NeocoderFork [session_id]:NeocoderCancel:NeocoderStop:NeocoderAcceptPatch:NeocoderRejectPatch:NeocoderStatus:NeocoderScope:NeocoderMCP:NeocoderDoctor:NeocoderTrace [turn_id]:NeocoderTools [server_id]:NeocoderSwitchProvider <adapter> [model]:NeocoderModel <model>:NeocoderProvider:NeocoderModels:NeocoderRepoMap:NeocoderRagStatus:NeocoderVerify:NeocoderDaemonStart:NeocoderDaemonStop:NeocoderDaemonReindex:NeocoderDaemon:NeocoderACPStatus:NeocoderPromptLog [limit]:NeocoderReplayPrompt <log_id>
Prompt tags to override scope:
@selection@file@module@repo@external@local_only/@no_external
Prompt directive DSL:
- Slash commands:
/new,/clear,/compact,/lines <start>-<end> <question>,/commit,/provider <adapter>,/model <model>,/stop - Mentions:
@file:<path>or@file <path>,@codebase,@diagnostics,@selection - Shortcuts:
#refactor,#test - Tool policy controls:
@disable_tool:<tool>,@enable_tool:<tool>,@approval:auto|ask|policy_default
Runtime behavior:
- NeoCoder infers prompt scope and emits
scope_decided. - NeoCoder computes an autonomy route and emits
autonomy_decision. - Retrieval stages gather evidence before planning.
- Retrieval includes repo-map symbols when enabled (
retrieval.repo_map.enabled = true). - Optional RAG retrieval stage runs when confidence is still low and
rag.enabled = true. - Evidence is ranked (
weighted_v2) and deduped before planner ingestion. - Planner receives compact evidence summary, not full raw dumps.
- Scope and retrieval traces are persisted in
.neocoder/sessions/<id>/retrieval.json. - Retrieval v2 output is persisted in
.neocoder/sessions/<id>/retrieval-v2.json. - Autonomy decisions are persisted in
.neocoder/sessions/<id>/decisions.jsonl.
When a tool step is evaluated, NeoCoder applies policy in this order:
- Turn directive (
@enable_tool,@disable_tool,@approval:*) - Session runtime overrides (
:Neocoder*actions persisted in state) - Provider adapter policy (
provider.adapter_policies[adapter]) - Global tool policy (
tools.auto_approve,tools.disabled_global) - Safety defaults (
safety.tool_policy.*) - Tool manifest defaults (
tool/manifest.lua+tools.manifest_overrides)
Example provider adapter policy:
require("neocoder").setup({
provider = {
adapter = "azure_openai",
adapter_policies = {
azure_openai = {
approval_mode = "ask_all",
disable_tools = { "bash" },
},
},
},
})Example global tool policy:
require("neocoder").setup({
tools = {
auto_approve = { "read_file", "grep", "diagnostics" },
disabled_global = { "bash" },
manifest_overrides = {
rag_search = {
requires_confirmation = true,
},
},
},
})Current MCP layer includes:
- Stdio JSON-RPC runtime (initialize/tools/list/tools/call)
- Dynamic registry cache (
.neocoder/mcp/registry-cache.json) - Project version pin lock (
.neocoder/mcp/lock.json) - Install approval flow (
install_policy = "ask_then_cache") - Runtime session pool + restart handling
- Local MCP surf: filesystem + git
- External MCP surf: github + fetch (auto when confidence is low)
Example config:
require("neocoder").setup({
scope = {
enabled = true,
mode = "adaptive",
},
retrieval = {
enabled = true,
strategy = "balanced",
ranker = "weighted_v2",
dedupe = "path_symbol_hash",
external_budget = {
max_calls = 3,
max_bytes = 180000,
},
repo_map = {
enabled = true,
max_files = 160,
max_defs_per_file = 16,
},
},
rag = {
enabled = false,
endpoint = "http://127.0.0.1:20250",
auto_index = false,
},
acp = {
enabled = false,
follow_agent_locations = true,
providers = {},
},
autonomy = {
enabled = true,
route_policy = "adaptive", -- adaptive | internal_only | external_first
max_subtasks = 3,
max_depth = 2,
external_escalation_threshold = 0.74,
},
mcp = {
enabled = true,
protocol = "stdio_jsonrpc",
registry_mode = "dynamic",
install_policy = "ask_then_cache",
external_policy = "auto_when_needed",
session_pool_size = 4,
server_ttl_ms = 300000,
max_restarts = 3,
},
})local neocoder = require("neocoder")
neocoder.on_event("event", function(ev)
-- ev.type, ev.payload, ev.session_id, ev.turn_id
end)- Session fork with history cloning (
:NeocoderFork) - Turn checkpoints at start/finish under
.neocoder/sessions/<id>/snapshots/ - Usage tracking in
.neocoder/sessions/<id>/usage.jsonl - MCP call traces in
.neocoder/sessions/<id>/mcp-calls.jsonl - Prompt logs in
.neocoder/sessions/<id>/prompt-log.jsonl
- NeoCoder runs schema migration during
setup(). - Current persistence schema version is written to
.neocoder/schema-version.json. - Last migration report is written to
.neocoder/migration-report.json.
- Hunk-aware patch object model (
version = 2) - Diff review keys:
aaccept all,rreject all,htoggle current hunk,j/kjump hunk - Safe no-op behavior when all hunks are rejected
- Plan normalization enforces max-step budget and final step
- Optional pre-execution plan review (
planner.review_before_execute = true)
<leader>nnask<leader>nvvisual<leader>nssearch<leader>nhhistory<leader>nttoggle workspace<leader>ncclear session<leader>nCcompact session<leader>nxcancel<leader>naaccept patch<leader>nrreject patch
docs/architecture.htmlarchitecture.html(redirects to docs page)docs/execution-trace.htmldocs/architecture.mmd
execution-trace.html now surfaces autonomy/retrieval/MCP lifecycle insights from:
.neocoder/sessions/<id>/events.jsonl.neocoder/sessions/<id>/decisions.jsonl.neocoder/sessions/<id>/mcp-calls.jsonl
make testTest matrix currently includes:
- Unit checks: scope tags, autonomy decisions, protocol encode/parse, budget enforcement.
- Integration checks: registry fallback behavior, install deny path, retrieval external escalation, lock finalize pin.
- E2E checks: full
neocoder.search()turn run with trace events (scope_decided,autonomy_decision,turn_done).