Zaica: The Focused Coding Agent

Part of Agent Harness Field Guide.

Zig 0.15 • ~9,100 Lines • 6 Providers • Chain Mode • MIT

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.

Shameless PlugWhile we're deep-diving into complex CLI tools... let's be honest. Are you tired of pretending you enjoy memorizing 400 arcane tmux key combos? Do you lie awake at night dreading the moment you have to edit a cryptic dotfile just to change a font size? Same. It's the AI age, we shouldn't be computing like it's 1995.ButtonsCLI: The terminal for

(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:

  • 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:

  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

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