Zaica: The Focused Coding Agent
A single static binary with zero runtime that delivers multi-provider LLM coding assistance, chain-mode structured workflows, parallel sub-agent dispatch, reactive state management, and a hand-crafted terminal REPL — all in ~9,100 lines of Zig.
What is Zaica?
Zaica (pronounced "ZAI-tsah") is a Zig AI coding assistant that runs
in your terminal, calls tools autonomously, and streams responses in
real-time. It is built from scratch in Zig 0.15 with
zero runtime dependencies beyond the standard library,
plus two optional dependencies: vaxis (terminal UI library)
and zefx (a custom reactive state management library, both
authored by the same developer).
The binary is distributed via Homebrew for macOS
(aarch64 + x86_64) and Linux (aarch64 + x86_64), with pre-built
binaries on GitHub releases. It uses std.http.Client
directly — no HTTP library dependency.
Key differentiator
Zaica is the most focused coding agent in the Zig category. While Wintermolt is a sprawling multi-feature platform, Zaica stays laser-focused on the coding loop: chat, tools, chains, skills, sessions — and does each one with exceptional polish.
Architecture overview
| Module | Files | Lines | What it does |
|---|---|---|---|
src/main.zig |
1 | ~100 | Entry point — 3 modes: single-shot, REPL, chain |
src/repl.zig |
1 | 2,153 | Full interactive REPL with manual line editing, UTF-8/Cyrillic, escape sequence parsing |
src/node.zig |
1 | 731 | Generic agentic loop — LLM call → parse → execute tools → repeat |
src/tools.zig |
1 | 631 | 7 tools with risk levels, permissions, output truncation |
src/chain.zig |
1 | 528 | Multi-step pipeline orchestrator (.chain.md files) |
src/state.zig |
1 | 600 | Reactive state graph using zefx (Events, Stores, watchers) |
src/session.zig |
1 | 544 | JSONL session persistence and resume |
src/skills.zig |
1 | 400 | Markdown-based knowledge injection system |
src/io.zig |
1 | 394 | Terminal I/O, scroll region, status bar, spinner |
src/config/ |
9 | ~1,500 | Layered JSON config system (6-layer priority chain) |
src/client/ |
4 | ~1,200 | HTTP streaming, SSE parser, message types, chat API |
The agentic loop (node.zig)
The central abstraction is the Node in
src/node.zig (731 lines). It is the generic agentic loop
used by both the REPL and sub-agents:
- LLM call — sends accumulated message history to the provider with streaming enabled
- Parse response — accumulates SSE deltas into content text and tool call fragments
- Execute tools — runs each tool call (parallel in terminal mode, sequential in silent mode)
- Append results — tool results become
tool_resultmessages in history - Repeat — up to the iteration limit, or until no more tool calls
Parallel tool execution is a key feature: in terminal
mode, tools run in parallel using std.Thread.spawn — each
tool executes in its own OS thread with a done-flag atomic
(std.atomic.Value(bool)) tracking completion. In
silent mode (sub-agents, chain mode), tools run
sequentially to avoid "threads-inside-threads."
Key file
src/node.zig — the Node struct with
run() as the main loop entry point,
executeToolsParallel() for parallel dispatch
Wyhash loop detection
Zaica implements a sophisticated loop detection system in
src/node.zig using a Wyhash-based ring buffer
of tool call signatures (tool name + args hash). It detects repeating
patterns of length 1, 2, or 3 within a 10-call window with a
3-tier escalation:
- Tier 1 — Warning message to the LLM: "You seem to be repeating the same actions"
- Tier 2 — Stronger "STOP" warning with explicit instruction to change approach
- Tier 3 — Force break the loop with a summary instruction
This is significantly more nuanced than simple tool-name deduplication. The same tool called with different arguments or producing different output gets a different hash.
6 provider support
All providers use the OpenAI-compatible /chat/completions
endpoint format except Anthropic, which uses the
.anthropic_native tool format. The wire format enum
(ToolFormat) is defined in
src/config/types.zig with three variants:
openai_compatible, anthropic_native, and
gemini_native.
| Provider | Base URL | Default Model | API Key Env Var |
|---|---|---|---|
| GLM (default) | api.z.ai |
glm-4.7-flash |
GLM_API_KEY |
| Anthropic | api.anthropic.com |
claude-sonnet-4-5-20250929 |
ANTHROPIC_API_KEY |
| OpenAI | api.openai.com |
gpt-4o |
OPENAI_API_KEY |
| OpenRouter | openrouter.ai |
openrouter/aurora-alpha |
OPENROUTER_API_KEY |
| DeepSeek | api.deepseek.com |
deepseek-chat |
DEEPSEEK_API_KEY |
| Ollama | localhost:11434 |
llama3 |
(none required) |
The HTTP client (src/client/http.zig, 388 lines) uses
Zig 0.15's std.http.Client API directly. It
disables compression (.accept_encoding = .omit)
because SSE needs a raw byte stream.
The SSE parser (src/client/sse.zig, 408 lines) parses
each data: {json} line and returns a tagged union with
variants: .content, .reasoning,
.tool_call_delta, .done (carrying token
usage), and .api_error.
The 7 tools
Zaica ships with exactly 7 tools, each with a declared risk level:
| Tool | Risk | Description |
|---|---|---|
read_file |
safe | Read file contents (10 MB limit) |
list_files |
safe | List directory entries |
search_files |
safe | Grep pattern search (/usr/bin/grep -rn) |
write_file |
write | Create or overwrite files |
execute_bash |
dangerous | Run bash commands (configurable timeout, default 30s) |
dispatch_agent |
dangerous | Spawn a parallel sub-agent for complex subtasks |
load_skill |
safe | Load a skill's full content on demand |
Tool output truncation uses a
head/tail strategy — keeping the first half and
last half of the output, with a truncation warning marker in the
middle. This aligns with the "Attractor coding-agent-loop-spec."
Per-type limits: read_file caps at 50,000 chars;
execute_bash at 30,000 chars / 256 lines.
3-tier permission system
Zaica's permission model is elegantly simple:
[y]es all— all tools allowed[s]afe only— only read-only tools (read_file,list_files,search_files)[n]o— nothing allowed
The ask happens once per session. The
--yolo / --dangerously-skip-permissions
CLI flag grants all tools automatically.
Bash execution wraps commands in a safety wrapper
that: redirects stdin from /dev/null (interactive
programs get EOF and exit immediately), sets a timer that kills the
process tree with pkill -9 -P $PID then
kill -9 $PID, detects SIGKILL (exit code 137) to report
timeout, and caps output at 1 MB.
Chain mode: structured workflows
Chain mode (src/chain.zig, 528 lines) is one of zaica's
most distinctive features. It allows you to define
multi-step pipelines in .chain.md
markdown files with YAML frontmatter:
Each step has: name, prompt (with
{task} and {previous} variable substitution),
optional tool filter, and
max_iterations.
Example: review.chain.md
4 steps: scout (read-only tools, 15 iterations) → planner (3 iterations) → coder (full tools) → reviewer (5 iterations). The chain determines the maximum risk level across all steps for a single permission prompt.
Reactive state management (zefx)
Instead of imperative state management, zaica uses a custom
Effector-inspired reactive state machine
(lib/zefx/). Events trigger Store reducers, which trigger
watchers. This gives a declarative data flow:
tokens_received event →
prompt_tokens / completion_tokens stores →
total_tokens derived store → status bar watcher.
The engine uses a two-phase flush (pure reducers first, then effects) ensuring watchers always see consistent state. This is a genuinely novel approach in a coding agent — no other agent in this set uses a reactive state graph for UI state.
Terminal-level control
Unlike Python agents that rely on libraries like rich or
prompt_toolkit, zaica does raw
/dev/tty access with manual
tcsetattr for cooked/uncooked/stream modes, manual
escape sequence parsing (CSI, CSI u), and direct ioctl for terminal
size.
It supports full UTF-8 and Cyrillic input including
Cyrillic keyboard layout mapping for Ctrl keys. The REPL
(src/repl.zig, 2,153 lines) implements manual line
editing with history — no readline dependency.
Skills system
Skills are SKILL.md files with JSON frontmatter in
.zaica/skills/<name>/ (project-local) or
~/.config/zaica/skills/<name>/ (user-global).
- "Always-on" skills are injected directly into the system prompt
- Other skills appear as an XML summary that the LLM can load on demand via
load_skill - Skills can declare required binaries/env vars; missing ones are reported to the LLM
Session management
Sessions are stored as JSONL files in
~/.config/zaica/sessions/<timestamp>.jsonl.
Each line is a typed JSON object: meta,
text, tool_use,
tool_result, or summary.
Supports listing, loading, resuming (-c /
--continue), and targeting specific sessions
(--session <id>).
6-layer config system
The config module (src/config/, 9 files, ~1,500 lines)
implements a 6-layer priority chain:
- Comptime defaults — hardcoded at compile time
- Provider presets — per-provider default models/settings
~/.config/zaica/config.json— user config.zaica.json— project-level configZAICA_*env vars — environment overrides- CLI flags — highest priority
API key resolution follows a 4-tier chain: CLI flag →
ZAICA_API_KEY → {PROVIDER}_API_KEY →
~/.config/zaica/auth.json.
Unique engineering decisions
Comptime JSON schemas
Tool parameter schemas are comptime string literals — zero allocation, validated at compile time. The request body is hand-written JSON with no serialization library — tool parameters are raw JSON schema strings written directly (not escaped).
ChatMessage as tagged union
.text, .tool_use, .tool_result —
type-safe message history enforced by the Zig compiler. No possibility
of a malformed message state at runtime.
Error-as-string pattern
Tool errors are returned as string descriptions (not Zig error unions)
so the LLM can reason about them.
"Error opening /path: error.FileNotFound" becomes context
for the next LLM call.
Head/tail output truncation
Unlike agents that truncate at a fixed boundary, zaica preserves the beginning and end of large outputs (where the most useful information typically is), with a clear truncation warning in the middle.
Multi-agent parallel dispatch
The dispatch_agent tool spawns sub-agents via
std.Thread.spawn running independent agentic loops with
full tool access — built-in parallelism that most single-threaded
Python agents lack.
Terminal resilience
Forces cooked mode on startup, \n →
\r\n translation regardless of OPOST, manual escape
sequence parsing — the terminal works correctly even on unusual
configurations.
Strengths
- Code quality — ~9,100 lines with extensive test coverage across all modules
- Focus — stays tight on the coding agent experience without feature creep
- Reactive state graph — the zefx engine is genuinely novel UI architecture
- Chain mode — structured workflows with per-step tool filtering
- Loop detection — Wyhash-based 3-tier escalation is the best in this set
- Terminal craftsmanship — raw /dev/tty, Cyrillic, manual line editing
- Parallel sub-agents — OS-thread-based dispatch for independent agent loops
Tradeoffs
Where Zaica gives things up
- Only 5 core tools — read_file, list_files, search_files, write_file, execute_bash. This is the minimal set for a coding agent, but less than the 16+ tools in Wintermolt or the 40+ in Claude Code.
- No MCP support — unlike Wintermolt (and many TypeScript agents), zaica doesn't implement the Model Context Protocol for extending its tool surface.
- No persistence layer — sessions are JSONL files, not a SQLite database. No semantic search, no RAG, no cross-session memory beyond the session file itself.
-
GLM is the default provider — the default model
is
glm-4.7-flashfrom Z.AI, not one of the more commonly tested models. This is an unusual default. -
Hardcoded grep path —
search_filesspawns/usr/bin/grepdirectly, which may not exist on all systems (e.g., BSD grep vs GNU grep paths). - Smaller ecosystem — no chat bridges, no web UI, no menu bar app, no gateway. It's a terminal-only experience.
Key files to read
Core runtime
src/node.zig (generic agentic loop, 731 lines),
src/repl.zig (full REPL, 2,153 lines),
src/tools.zig (7 tools + permissions, 631 lines),
src/chain.zig (structured workflows, 528 lines)
State and sessions
src/state.zig (reactive state graph, 600 lines),
src/session.zig (JSONL persistence, 544 lines),
src/skills.zig (knowledge injection, 400 lines),
src/io.zig (terminal I/O, 394 lines)
Config and client
src/config/ (9 files, layered JSON, ~1,500 lines),
src/client/http.zig (streaming HTTP, 388 lines),
src/client/sse.zig (SSE parser, 408 lines),
src/client/message.zig (ChatMessage union, 316 lines)
Reactive engine
lib/zefx/ (custom reactive state machine, Effector-inspired)
Verdict
Best designed, if you value code craftsmanship
Zaica is the most carefully crafted coding agent in the Zig category. At ~9,100 lines, it delivers a complete agentic experience with exceptional attention to terminal control, type safety (tagged unions throughout), and reactive state management.
Most focused on the coding loop
While Wintermolt goes wide with 7 modes and dozens of features, zaica goes deep on the core loop: chat, tools, chains, skills, sessions. The chain mode with structured workflows is genuinely unique — no other agent in this set offers it.