Getting Started

Zap is a single Rust binary — no Python venv, no Node, no Docker. Install it and run it from any directory.

< 10 ms
Cold start
~9 MB
Binary size
0
Runtime deps
1

Download & install

curl -fsSL https://raw.githubusercontent.com/zap-coding-agent/zap-coding-agent/main/install.sh | bash

Pre-built binary, installed to ~/.local/bin. Works on both Apple Silicon and Intel Macs.

2

Set your API key

# Add to ~/.zshrc export ANTHROPIC_API_KEY=sk-ant-...

Or configure Gemini, OpenAI, or a local model — see Configuration.

3

Run

cd your-project && zap

Type /init on first use to build the code index and generate a project summary.

1

Download & install

curl -fsSL https://raw.githubusercontent.com/zap-coding-agent/zap-coding-agent/main/install.sh | bash

Pre-built binary, installed to ~/.local/bin. Supports x86_64 and arm64.

2

Set your API key

# Add to ~/.bashrc or ~/.zshrc export ANTHROPIC_API_KEY=sk-ant-...
3

Run

cd your-project && zap
1

One-line install (PowerShell)

irm https://raw.githubusercontent.com/zap-coding-agent/zap-coding-agent/main/install.ps1 | iex

Pre-built binary, installed automatically.

2

Set your API key

# PowerShell — add to $PROFILE for persistence $env:ANTHROPIC_API_KEY = "sk-ant-..."
3

Run

cd your-project; zap
Recommended: run /init after installing.

This builds the code graph and generates a project summary. It takes 10–30 seconds and runs once per project. With the index active, the model knows every symbol, every call site, every import, and which files are load-bearing — typically 3–5 fewer tool calls per task and no guesswork on impact analysis.

After big refactors, run /index to rebuild. See Code Graph ↓ for the analogy, the schema, and five real examples that show what changes.

Configuration

All settings live in ~/.agent.toml. Create the file if it doesn't exist — zap reads it on every startup.

Minimal config
# ~/.agent.toml model = "claude-sonnet-4-6" [anthropic] api_key = "sk-ant-..." # or use ANTHROPIC_API_KEY env var
All options
# ~/.agent.toml model = "claude-sonnet-4-6" mode = "ask" # ask | auto skill_paths = ["~/.claude/skills", "~/.zap/skills"] disabled_tools = ["shell", "web_fetch"] # tools the LLM will never see disabled_skills = ["deploy", "ship"] # skills never auto-triggered [model_routes] # route tasks to specific models coding = "codex/gpt-5.5" review = "claude-opus-4-8" [anthropic] api_key = "sk-ant-..." [gemini] api_key = "AIza..." [openai] api_key = "sk-..." base_url = "https://api.openai.com/v1" # override for local models [deepseek] api_key = "sk-..."
Tip: Set mode = "auto" to let zap run tools without asking for confirmation each time. Useful for trusted projects.
Option reference
Key Default Description
model "claude-sonnet-4-6" Model to use for all LLM calls. Any provider model string is accepted.
mode "ask" ask — prompt before each tool call. auto — run tools without confirmation. Switch per project via /mode.
skill_paths [] Directories scanned for skill files (.md). Skills found here are available for keyword-triggered injection.
disabled_tools [] Tool names to exclude from every session. The LLM will not see or be able to call excluded tools. Use /tools inside zap to list all available names. Example: disabled_tools = ["shell", "web_fetch"]
disabled_skills [] Skill names to exclude from automatic injection. Skills matching this list are never triggered, even if the user's message matches their keywords. Example: disabled_skills = ["deploy", "ship"]
[model_routes] none Map task types to specific model slugs. Supported keys: coding, review, explain, search. When a matching message is detected, zap shows an approval prompt before routing to the configured model. The current session model is unchanged after the turn. Example:
[model_routes]
coding = "codex/gpt-5.5"
review = "claude-opus-4-8"

Project Init

Run /init once in a new project and zap goes from blank-slate to fully context-aware in about 30 seconds. It builds the code index, reads your source files, and writes persistent knowledge files the agent loads on every future session.

~30s
Time to full context
4 files
Written automatically
0
Manual steps after
What /init does

Detects your stack

Identifies language and framework from manifests (Cargo.toml, package.json, pom.xml) and fires the right skill automatically.

Builds the code graph

Runs tree-sitter across your repo and writes symbols, call sites, imports, and PageRank-ranked files to .zap/code.db. The model queries the graph before writing anything new.

Writes project knowledge

Asks the LLM to read your source and fill in ZAP.md — build commands, architecture, key files, do-not-touch list.

Files created
FileWhat it containsLoaded when
ZAP.mdProject overview, build/test commands, architecture, do-not-touch listEvery session
.zap/understanding.mdNavigation map (module layout, entry points, constraints) + business-domain map (added by /understand)Every session
.zap/context.mdLast session: goal, files touched, what's next (auto-updated on exit)Session start
.zap/session_log.mdHistory of all past sessions indexed by dateOn request
Example ZAP.md output
## Overview Order service — handles order lifecycle (create, fulfil, cancel). ## Build & Test mvn clean install && mvn test ## Architecture - OrderController → REST handlers - OrderService → business logic, calls OrderRepository - OrderRepository → JPA, Postgres via spring-data ## Do Not Touch - LegacyOrderMapper.java — deprecated, backwards compat only
After /init: Every future session starts informed. The agent already knows your build commands, architecture, and what you worked on last time. You never re-explain the project.

Domain Map /understand

Run /understand after /init to extract a structured business-domain map — which modules own auth, billing, storage, and so on. One LLM call, pure code_map output, no source-file reads needed.

1
LLM call
0
Source files read
Auto
Staleness detection
What it produces

Business Domains table

Each distinct concern — auth, billing, queuing, etc. — mapped to the files and key function names that implement it. Navigate straight to the right module, no guessing.

Cross-Cutting Concerns

Infrastructure every domain touches: error handling, logging, config, DB, serialization. Helps the agent know what to check before changing something low-level.

Dependency direction

One-line layering rule (e.g. tools → session → agent_core; nothing imports upward) so the agent never suggests a circular import.

Real output — run on the zap repo itself

Two code_map calls, zero source-file reads, one edit_file write. Total: ~80 seconds including LLM round-trip.

## Domain Map ### Business Domains | Domain | Owns | Key entry points | |--------------------------|-------------------------------------------|-------------------------------| | Agent runtime | agent_core, cli, main, lib | authenticate, Session::new | | LLM provider integration | llm_client/*, http | send, stream | | Tool execution + safety | tools/*, permission_manager, shell_runner | ToolRegistry::execute | | Project context + prompt | context_manager, project, plan_execution | build_system_prompt | | Code intelligence | code_index/* | index_dir, global_callers_of | | MCP integration | mcp | mcp_connect, list_tools | | Persistence + memory | persistence, project | init, save_session_context | | Skills + workflow | skill_manager, default_skills/* | detect_domain_scope | | Remote session sharing | remote, remote_channel | spawn_tunnel, broadcast | ### Cross-Cutting Concerns - Error handling: anyhow::Result everywhere; tool errors surfaced as text responses - Logging: crate::log::write (file-only, never stdout) - Config: Config struct in src/config.rs, loaded once at startup - Security: permission_manager, secret_scanner, audit enforce approval + logging ### Dependency Direction tools → session → agent_core; llm_client ← session only; nothing imports session from tools
Staleness detection

Zap records how many source modules existed when /understand last ran. If the count drifts more than 10% (new modules added, old ones removed), the system prompt shows a nudge — "project structure has changed — run /understand to update the domain map" — so the map stays accurate without any manual bookkeeping.

Workflow: /init/understand → done. The domain map is injected into every future session automatically alongside the navigation map.

Skill-First Context

Skills are plain markdown files that zap injects into the context window before the model sees your message. The key insight: only relevant skills fire. A greeting costs 31 tokens. A Rust question gets the Rust skill injected automatically.

How it works
"hi, how are you?" no skills(31 tokens)
"refactor UserStore to use channels" ▶ rust(+650 tokens)
"add a useEffect to fetch user data" ▶ react(+422 tokens)
"write a test for the login handler" ▶ rust ▶ testing(+890 tokens)
Skill types
Type When it fires Example
Core Every message, always injected CLAUDE.md, project context
Trigger When your message matches a keyword trigger Rust, React, Python, testing skills
Skill file format

Skills are markdown files with an optional YAML frontmatter block. Files without frontmatter are always-on Core skills.

--- name: rust description: Rust coding conventions and best practices trigger: ["rust", "cargo", "tokio", "async"] --- # Rust - Prefer `Result<T, E>` over panic for recoverable errors - Use `clippy` for lints — treat warnings as errors in CI - Async: use `tokio::spawn` for concurrent tasks...
Where to put skills

Project skills

Drop .md files in .zap/skills/ inside your project. Checked in with your repo — shared with your team.

Global skills

Put skills in ~/.zap/skills/ or configure skill_paths in ~/.agent.toml to point at any directory.

Cross-agent compatibility

Skills created for Claude Code or Gemini work in zap. Point skill_paths at ~/.claude/skills to share them automatically. This means you can keep the prompts and workflows you like while gaining Zap's code index, memory, context controls, and multi-provider runtime.

# ~/.agent.toml skill_paths = ["~/.claude/skills", "~/.zap/skills"]
Pro tip: Use /skills in a session to see which skills are loaded and which triggered on the last message.

Code Graph

Most AI agents grep their way around your codebase. zap builds a structural map — what exists, what calls what, what imports what, and which files everything depends on — so the model answers questions in one query instead of guessing across ten.

The building analogy

Think of your project as a building.

A file = a room

Each source file is a room in the building. config.rs is one room, cli.rs is another.

A function = furniture

Each function, struct, or class is a piece of furniture in a room — with a known name and position.

An AST = the room blueprint

Every wall, outlet, and door in one room. Tree-sitter produces this for each file as it parses.

A symbol = floor plan summary

Just the labels for the key furniture and where it sits — name + line number + signature. The B-tier of code indexing.

A graph = doorways & corridors

The hallways connecting rooms. "This function in room A calls that one in room C." "Room B imports furniture from room D."

PageRank = foot traffic

Which rooms get used most — the lobby, the cafeteria, the main staircase. The load-bearing files.

Most code intelligence tools stop at floor plans. zap draws the doorways too, then measures the foot traffic. That's what turns "grep and pray" into "look it up."
What zap actually stores

Running /init (and every file change after) walks your source with tree-sitter and writes four kinds of facts to .zap/code.db:

Table What it records Real example from zap's own codebase
symbols Where every function, struct, class is defined find_references is a function at src/code_index/index_impl.rs:395
call_sites Every place one function calls another mod.rs:205 calls index_file from inside fn global_reindex_file
imports Every use / import / using statement config.rs imports HashMap from std::collections
file_rank PageRank score per file — its structural centrality tools/todo.rs ranks 0.0208 — the orchestrator everything depends on

Indexes the zap codebase in under 5 seconds: 264 files, 4,294 symbols, 20,272 call sites, 2,064 imports.

How this reduces guesswork — real examples

Every example below is a task you'd give an AI agent. The left column is what happens without a graph (most agents). The right column is what zap does in one query.

Example 1 · "Where is UserStore used?"
Without the graph
# 5+ tool calls, 47 noisy hits grep "UserStore" → 47 matches: docs, tests, comments, string literals, the definition, function bodies, error messages… read_file(...) # try to disambiguate read_file(...) read_file(...) # Still not sure which are real callers
With the graph
# 1 query — exact answers find_references("UserStore") → 19 real call sites, each labelled with the enclosing function: · impl Session · save_context_inner · fn boot · impl Handler · on_request …sorted by load-bearing file first
Example 2 · "If I rename register_provider, what breaks?"
Without the graph
# Grep and pray grep "register_provider" → misses aliases: use crate::reg::register_provider as register; register(...) # <- missed! # Rename ships. Build breaks.
With the graph
# Blast radius in 2 queries where_imported("register_provider") → 4 files import it (including the aliased one) find_references("register_provider") → 7 call sites — every aliased call surfaces because the index tracked the alias at import time.
Example 3 · "Refactor the retry logic"

The hard kind of task — there's no symbol literally named "retry." The logic is scattered across handlers, configs, and helpers.

Without the graph
# Fuzzy grep, 200 matches grep -i "retry" → comments, error strings, variable names, 8 real symbols read_file(...) × 8 files # ~3,000 tokens burnt before the agent # even knows where to start editing.
With the graph
# Curated bundle in one call pack_context( task: "refactor retry logic", token_budget: 4000 ) → ranked symbols + callers + importers, signatures only, with provenance: · "name match · 3 hits" · "caller of retry_with_backoff" · "importer of RetryConfig"
Example 4 · "Add a new LLM provider — what's the pattern?"
Without the graph
# Read multiple providers cover-to-cover read_file("anthropic.rs") # ~800t read_file("openai.rs") # ~700t read_file("gemini.rs") # ~750t # ~2,500 tokens to learn the convention
With the graph
# Pattern visible in 2 queries file_imports("llm_client/anthropic.rs") → what an existing provider needs find_references("register_provider") → where every provider plugs in # ~200 tokens. Pattern obvious.
Example 5 · "What's most important — touch carefully"
Without the graph
# Subjective. Read READMEs. # Skim folder structure. # Hope you know what's load-bearing.
With the graph
# PageRank tells you rank_files(top: 10) → 1. tools/todo.rs 0.0208 2. llm_client/credentials.rs 3. code_index/index_impl.rs 4. tui/render.rs …files everything depends on
The pattern: every example replaces "agent reads 5 files and guesses" with "agent asks the graph and knows." Fewer tool calls, fewer tokens burnt, and — critically — no hallucinated answers. The graph either knows or it doesn't, and when it doesn't it falls back to text search transparently.
Code intelligence tools

You don't have to think about any of this. The agent picks the right tool for each question automatically:

Tool What it does When the agent reaches for it
find_references Every call site for a symbol, ranked by file importance Impact analysis, refactor planning
who_calls Same as above, narrowed by qualifier (e.g. only Bar::foo) Disambiguation when many symbols share a name
file_imports List every use / import in a file Understanding scope before editing
where_imported Every file that imports a given name or module Blast-radius check before a rename
pack_context Curated context bundle within a token budget (signatures + provenance) Loading the right code for a multi-file task
ripple_analysis BFS walk of the call graph — direct callers, callers of callers, full transitive blast radius Before renaming a function, changing a signature, or deleting a symbol
get_diagnostics Live compiler errors and warnings via language server — no full compile needed After editing a file, to confirm it's correct before moving on
lsp_definition Type-resolved go-to-definition for cross-crate symbols (std, deps, generics, trait impls) When AST index says "not found" — usually an external symbol
lsp_type_at Exact inferred type of any expression — the editor tooltip, in the agent When the type of an expression isn't obvious from reading the code
Supported languages
Language Symbols Call graph Import graph
Rust
Python
JavaScript / TypeScript / TSX
Go
Java
C#
Querying the index yourself

The database is plain SQLite. If you want to poke at it directly:

-- Top 10 most-called functions in your repo SELECT name, COUNT(*) AS calls FROM call_sites GROUP BY name ORDER BY calls DESC LIMIT 10; -- Find all the load-bearing files SELECT printf('%.4f', rank) AS rank, path FROM file_rank ORDER BY rank DESC LIMIT 20; -- Who calls a specific qualified function? SELECT path, line, caller_scope FROM call_sites WHERE name = 'write' AND qualifier = 'crate::log';
The deliberate tradeoff

zap resolves names, not types. That means foo.bar() matches any bar with that name — not the one a full type-checker would resolve to. The win: queries finish in microseconds, in-process, with zero external dependencies. The cost: when several symbols share a name, the agent gets all matches and disambiguates with a qualifier filter (who_calls). In practice this is rarely the bottleneck — agents read the file anyway to confirm.

For queries that need full type resolution — cross-crate symbols, generic instantiations, trait impls — zap now ships three LSP tools that query a running language server: get_diagnostics (instant compiler errors), lsp_definition (type-resolved go-to-definition), and lsp_type_at (inferred type of any expression). AST tools answer structural questions in microseconds; LSP tools answer semantic questions when precision matters. Both layers work together.

Real-world example: In a live code review, zap (with index) identified that the same 13-provider list was duplicated across two files — and flagged it as a Phase 0 refactor risk. Claude Code (without index, no grep) missed it entirely. See the full case study →

Context Visibility

Zap shows you exactly what's in the context window — token counts per message, which skills fired, and total usage. You can inspect, manage, and trim the context without starting a new session.

Commands
Command What it does
/context List all messages with their index and token count
/context drop 3 Remove message at index 3 (keeps the rest)
/context drop 3-7 Remove messages 3 through 7
/context clear Wipe the entire context — keeps skills and system prompt
Example output
[0] system 1,889 tokens (rust skill injected) [1] user 42 tokens "refactor UserStore to use channels" [2] assistant 630 tokens [3] user 28 tokens "looks good — add tests" [4] assistant 412 tokens ────────────────────────────────── total 3,001 tokens of 200,000 available
Why this matters: Most agents let the context balloon silently. When you're 180,000 tokens in and the model starts confusing earlier code with newer changes, you now have a scalpel instead of a restart button.

Casual Messages

Not every message is a coding task. When you greet zap or ask something unrelated to your project, it skips skill injection entirely — saving the tokens for when they count.

Casual message
"hi there" 31 tokens
"what's the weather?" 36 tokens
"thanks!" 28 tokens
Coding message
"fix the async error" ▶ rust · 650 tokens
"add a loading state" ▶ react · 422 tokens
"write unit tests" ▶ rust ▶ test · 890t

The classifier runs locally and adds zero latency — it's a keyword heuristic, not a model call. It errs on the side of injecting skills when uncertain.

Dynamic MCP Loading

Zap supports the Model Context Protocol. MCP servers start pending — their tool schemas don't enter the context until explicitly needed. No 10,000-token schema dump on every turn.

Configuration

Zap reads MCP config from ~/.zap/mcp.json (global) and .mcp.json (project-local). Both use the same Claude Code-compatible format.

// ~/.zap/mcp.json { "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"] }, "postgres": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres"], "env": { "DATABASE_URL": "postgresql://localhost/mydb" } } } }
How it differs from other agents

Other agents

Dump all MCP tool schemas into every context window at startup — even tools you never call.

Zap

Servers start idle. Schemas enter context only when the model decides to call that tool — keeping baseline cost near zero.

Compatibility: If you already have ~/.claude/mcp.json or a Claude Code MCP config, zap reads the same format. No migration needed.

Multi-Provider

Zap works with every major AI provider — and local models. Switch mid-session without restarting. The important part is that the surrounding agent stays the same: memory, tooling, indexing, permissions, and workflows do not disappear just because you changed models.

Claude Anthropic — claude-sonnet-4-6, claude-opus-4-7, haiku-4-5
Gemini Google — gemini-2.5-pro, gemini-2.5-flash
OpenAI GPT-4o, GPT-4o-mini, o3
DeepSeek deepseek-chat, deepseek-reasoner
LM Studio Any local model via OpenAI-compatible endpoint
Ollama llama3, mistral, codestral, and more
Switching providers
# Switch model mid-session /model gemini-2.5-pro # Or set a default in config model = "gemini-2.5-flash" # ~/.agent.toml
Why this matters

Not locked to Gemini CLI or OpenCode

If you like Gemini models but want stronger repo navigation, memory, and context control, you can use Gemini inside Zap instead of adopting a separate tool stack.

Keep one workflow across models

Teams can standardize on one set of slash commands, skills, and review habits even when different developers prefer Claude, Gemini, OpenAI, or local models.

Power without extra friction

Switching providers does not require reinstalling a new agent or relearning how it edits files, asks permission, searches code, or resumes past work.

Concrete examples
# Use Gemini for broad reasoning on a planning task /model gemini-2.5-pro # Later switch to Claude or DeepSeek for implementation /model claude-sonnet-4-6 # Your skills, memory, code index, and tool workflow stay the same
Usability advantage: with Gemini CLI or OpenCode, the model choice and the agent experience are bundled together. Zap separates them, so you can choose the model you want without giving up the agent features you rely on.
Local models (LM Studio / Ollama)
# ~/.agent.toml — point at any OpenAI-compatible server [openai] api_key = "lm-studio" base_url = "http://localhost:1234/v1" model = "lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF"

Security Boundary

Zap is explicit about what it touches. Every shell command and file write is shown before it runs. You confirm — or deny — each action.

Zero telemetry

No usage data, no crash reports, no analytics. Your code and queries stay between you and your chosen model provider.

Keys stay local

API keys live in ~/.agent.toml or environment variables. Never logged, never sent anywhere other than the provider API.

Ask mode default

Tool calls (file edits, shell commands) require confirmation by default. Switch to auto only for trusted projects.

Modes
mode = "ask" # default: confirm every tool call mode = "auto" # run tools without prompting (trusted projects)
MIT License

Zap is open source under the MIT License. Read the code, fork it, audit it. The binary you run is built from the public source.

Session Memory

Zap keeps a running log of what was worked on and which files were touched. When you start a new session, the model has context from previous ones — without bloating the live context window.

How it works

.zap/context.md

Last session's work, files touched, and what's next. Updated by zap at the end of each session.

.zap/session_log.md

A brief history of past sessions — goal and files per session. Grows incrementally, never overwrites.

Project memory file

You can also create a CLAUDE.md (or .zap/PROJECT.md) with permanent project-level context that fires on every message as a Core skill.

# CLAUDE.md — project context, always injected # My Project This is a Rust web server using Axum and SQLx. Postgres 15 runs locally on port 5432. Tests use testcontainers — don't mock the database.
Tip: Keep CLAUDE.md concise. It fires on every message — every token in it is spent whether the model needs it or not. Put language-specific context in trigger skills instead.

Autonomous Mode

/goal <condition> runs turns automatically until the model signals it's done or a turn limit is reached. Use it for multi-step tasks you'd otherwise have to shepherd turn-by-turn.

How it works
# Run until done, up to 20 turns (default) /goal add pagination to the GET /orders endpoint and write tests # Set a custom turn limit /goal fix all clippy warnings --max 10 # Stop mid-flight /goal stop

The model runs tool calls autonomously each turn. When it's finished it ends its response with ✓ DONE and the loop stops. The sidebar shows the goal condition, current turn, and elapsed time while it runs. Ctrl+C cancels at any point.

Good for

Multi-file refactors, adding full feature layers (controller + service + tests), fixing all lints, running a test suite and fixing failures.

Permission mode

Goal mode respects your current permission mode. Use /mode auto before /goal to let it run without confirmation prompts on every tool call.

Tip: Be specific about the done condition. "Refactor auth module" is vague — "refactor auth module so all tests pass and cargo clippy has zero warnings" gives the model a clear signal to stop.

Scheduler

Zap can run recurring goals on a schedule inside the TUI. Use it for background maintenance tasks like dependency checks, inbox triage, changelog drafting, or periodic repo health scans. It's one of the clearest examples of Zap being powerful in day-to-day use, not just in one-shot prompts.

Natural intervals

Create jobs with compact intervals like 30m, 1h, or a clock time such as 17:30.

TUI slash commands

Manage jobs with /schedule <interval> <goal>, inspect them with /schedule list, and stop them with /unschedule <name>.

Status visibility

The TUI status bar shows active job count, so you can see scheduled automation without leaving your current session.

Safe by default

Scheduled jobs still run through the same model, tool, and permission boundaries as normal Zap turns.

Example
/schedule 1h run cargo test, summarize failures, and update the task plan if anything broke
Note: Scheduler commands are currently available in the TUI flow. If you don't see them in your current mode, open the full-screen interface and run /help there.

Commands Reference

Type any command at the zap prompt. Tab-completion is available for all slash commands.

Command Description
/initBuild the code index and write project knowledge files (ZAP.md, understanding.md)
/understandExtract a business-domain map from the code index — one LLM call, no file reads
/indexRebuild the code index after large refactors
/skillsList loaded skills and which triggered last
/contextShow context window with token counts per message
/context drop NDrop message at index N
/context drop N-MDrop messages from index N through M
/context clearClear all messages (keeps skills and system prompt)
/model <name>Switch model for the current session
/provider <name>Switch AI provider mid-session
/mode askRequire confirmation for every tool call
/mode autoRun tools without confirmation prompts
/schedule <interval> <goal>Schedule a recurring goal in the TUI (examples: 30m, 1h, 17:30)
/schedule listShow active scheduled jobs in the current TUI session
/unschedule <name>Cancel a scheduled job by name
/helpShow all available commands
/exitEnd the session (Ctrl+C also works)