AI Coding Guides Deep Dives
Zig 0.15 • ~9,100 Lines • 6 Providers • Chain Mode • MIT

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.

(Alright, ad over. Back to the serious technical analysis.)

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:

  1. LLM call — sends accumulated message history to the provider with streaming enabled
  2. Parse response — accumulates SSE deltas into content text and tool call fragments
  3. Execute tools — runs each tool call (parallel in terminal mode, sequential in silent mode)
  4. Append results — tool results become tool_result messages in history
  5. 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:

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:

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).

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:

  1. Comptime defaults — hardcoded at compile time
  2. Provider presets — per-provider default models/settings
  3. ~/.config/zaica/config.json — user config
  4. .zaica.json — project-level config
  5. ZAICA_* env vars — environment overrides
  6. 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

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-flash from Z.AI, not one of the more commonly tested models. This is an unusual default.
  • Hardcoded grep pathsearch_files spawns /usr/bin/grep directly, 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.